From 05b10d86273f2da74e787bb37718b2a31ea6eda3 Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Fri, 30 Dec 2022 15:10:21 +0200 Subject: [PATCH 1/6] the very bare minimum typing fixes I need now. Sorry :/ --- src/textual/_node_list.py | 11 ++++++----- src/textual/css/errors.py | 5 ++--- src/textual/css/tokenizer.py | 2 +- src/textual/devtools/server.py | 6 +++--- src/textual/geometry.py | 5 ++++- src/textual/pilot.py | 12 ++++++------ src/textual/renderables/sparkline.py | 23 +++++++++++++---------- src/textual/renderables/text_opacity.py | 7 ++++--- src/textual/strip.py | 4 ++-- src/textual/widgets/_tree.py | 18 +++++++++--------- 10 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/textual/_node_list.py b/src/textual/_node_list.py index 7032b0cc7b..5a464405a6 100644 --- a/src/textual/_node_list.py +++ b/src/textual/_node_list.py @@ -1,6 +1,7 @@ from __future__ import annotations +import sys -from typing import TYPE_CHECKING, Iterator, Sequence, overload +from typing import TYPE_CHECKING, Any, Iterator, Sequence, overload import rich.repr @@ -13,7 +14,7 @@ class DuplicateIds(Exception): @rich.repr.auto(angular=True) -class NodeList(Sequence): +class NodeList(Sequence[Widget]): """ A container for widgets that forms one level of hierarchy. @@ -46,10 +47,10 @@ def __rich_repr__(self) -> rich.repr.Result: def __len__(self) -> int: return len(self._nodes) - def __contains__(self, widget: Widget) -> bool: + def __contains__(self, widget: object) -> bool: return widget in self._nodes - def index(self, widget: Widget) -> int: + def index(self, widget: Any, start: int = 0, stop: int = sys.maxsize) -> int: """Return the index of the given widget. Args: @@ -61,7 +62,7 @@ def index(self, widget: Widget) -> int: Raises: ValueError: If the widget is not in the node list. """ - return self._nodes.index(widget) + return self._nodes.index(widget, start, stop) def _get_by_id(self, widget_id: str) -> Widget | None: """Get the widget for the given widget_id, or None if there's no matches in this list""" diff --git a/src/textual/css/errors.py b/src/textual/css/errors.py index c0a5ca95d9..a326db43b0 100644 --- a/src/textual/css/errors.py +++ b/src/textual/css/errors.py @@ -4,8 +4,7 @@ from rich.traceback import Traceback from ._help_renderables import HelpText -from .tokenize import Token -from .tokenizer import TokenError +from .tokenizer import Token, TokenError class DeclarationError(Exception): @@ -32,7 +31,7 @@ class StyleValueError(ValueError): error is raised. """ - def __init__(self, *args, help_text: HelpText | None = None): + def __init__(self, *args: object, help_text: HelpText | None = None): super().__init__(*args) self.help_text = help_text diff --git a/src/textual/css/tokenizer.py b/src/textual/css/tokenizer.py index 51c8bf622f..ca5094fc8b 100644 --- a/src/textual/css/tokenizer.py +++ b/src/textual/css/tokenizer.py @@ -242,7 +242,7 @@ def skip_to(self, expect: Expect) -> Token: while True: if line_no >= len(self.lines): raise EOFError( - self.path, self.code, line_no, col_no, "Unexpected end of file" + self.path, self.code, (line_no, col_no), "Unexpected end of file" ) line = self.lines[line_no] match = expect.search(line, col_no) diff --git a/src/textual/devtools/server.py b/src/textual/devtools/server.py index ab13ca8ee5..3e32f628fd 100644 --- a/src/textual/devtools/server.py +++ b/src/textual/devtools/server.py @@ -40,8 +40,8 @@ async def _on_startup(app: Application) -> None: def _run_devtools(verbose: bool, exclude: list[str] | None = None) -> None: app = _make_devtools_aiohttp_app(verbose=verbose, exclude=exclude) - def noop_print(_: str): - return None + def noop_print(_: str) -> None: + pass try: run_app( @@ -80,4 +80,4 @@ def _make_devtools_aiohttp_app( if __name__ == "__main__": - _run_devtools() + _run_devtools(True) diff --git a/src/textual/geometry.py b/src/textual/geometry.py index e29790e304..2dbb2c22ae 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -7,6 +7,7 @@ from __future__ import annotations from functools import lru_cache +import math from operator import attrgetter, itemgetter from typing import Any, Collection, NamedTuple, Tuple, TypeVar, Union, cast @@ -129,7 +130,7 @@ def get_distance_to(self, other: Offset) -> float: """ x1, y1 = self x2, y2 = other - distance = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ** 0.5 + distance = math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) return distance @@ -218,6 +219,8 @@ def contains_point(self, point: tuple[int, int]) -> bool: def __contains__(self, other: Any) -> bool: try: x, y = other + assert isinstance(x, int) + assert isinstance(y, int) except Exception: raise TypeError( "Dimensions.__contains__ requires an iterable of two integers" diff --git a/src/textual/pilot.py b/src/textual/pilot.py index 5d0427905b..d13d1bb8d3 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -3,24 +3,24 @@ import rich.repr import asyncio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Generic if TYPE_CHECKING: - from .app import App + from .app import App, ReturnType @rich.repr.auto(angular=True) -class Pilot: +class Pilot(Generic[ReturnType]): """Pilot object to drive an app.""" - def __init__(self, app: App) -> None: + def __init__(self, app: App[ReturnType]) -> None: self._app = app def __rich_repr__(self) -> rich.repr.Result: yield "app", self._app @property - def app(self) -> App: + def app(self) -> App[ReturnType]: """App: A reference to the application.""" return self._app @@ -47,7 +47,7 @@ async def wait_for_animation(self) -> None: """Wait for any animation to complete.""" await self._app.animator.wait_for_idle() - async def exit(self, result: object) -> None: + async def exit(self, result: ReturnType) -> None: """Exit the app with the given result. Args: diff --git a/src/textual/renderables/sparkline.py b/src/textual/renderables/sparkline.py index 5630c6bb6c..eb55a6d56b 100644 --- a/src/textual/renderables/sparkline.py +++ b/src/textual/renderables/sparkline.py @@ -1,7 +1,7 @@ from __future__ import annotations import statistics -from typing import Sequence, Iterable, Callable, TypeVar +from typing import Generic, Sequence, Iterable, Callable, TypeVar from rich.color import Color from rich.console import ConsoleOptions, Console, RenderResult @@ -12,8 +12,9 @@ T = TypeVar("T", int, float) +SummaryFunction = Callable[[Sequence[T]], float] -class Sparkline: +class Sparkline(Generic[T]): """A sparkline representing a series of data. Args: @@ -33,16 +34,16 @@ def __init__( width: int | None, min_color: Color = Color.from_rgb(0, 255, 0), max_color: Color = Color.from_rgb(255, 0, 0), - summary_function: Callable[[list[T]], float] = max, + summary_function: SummaryFunction[T] = max, ) -> None: - self.data = data + self.data: Sequence[T] = data self.width = width self.min_color = Style.from_color(min_color) self.max_color = Style.from_color(max_color) - self.summary_function = summary_function + self.summary_function: SummaryFunction[T] = summary_function @classmethod - def _buckets(cls, data: Sequence[T], num_buckets: int) -> Iterable[list[T]]: + def _buckets(cls, data: Sequence[T], num_buckets: int) -> Iterable[Sequence[T]]: """Partition ``data`` into ``num_buckets`` buckets. For example, the data [1, 2, 3, 4] partitioned into 2 buckets is [[1, 2], [3, 4]]. @@ -73,13 +74,15 @@ def __rich_console__( minimum, maximum = min(self.data), max(self.data) extent = maximum - minimum or 1 - buckets = list(self._buckets(self.data, num_buckets=self.width)) + buckets = tuple(self._buckets(self.data, num_buckets=width)) bucket_index = 0 bars_rendered = 0 - step = len(buckets) / width + step = len(buckets) // width summary_function = self.summary_function min_color, max_color = self.min_color.color, self.max_color.color + assert min_color is not None + assert max_color is not None while bars_rendered < width: partition = buckets[int(bucket_index)] partition_summary = summary_function(partition) @@ -94,10 +97,10 @@ def __rich_console__( if __name__ == "__main__": console = Console() - def last(l): + def last(l: Sequence[T]) -> T: return l[-1] - funcs = min, max, last, statistics.median, statistics.mean + funcs: Sequence[SummaryFunction[int]] = (min, max, last, statistics.median, statistics.mean) nums = [10, 2, 30, 60, 45, 20, 7, 8, 9, 10, 50, 13, 10, 6, 5, 4, 3, 7, 20] console.print(f"data = {nums}\n") for f in funcs: diff --git a/src/textual/renderables/text_opacity.py b/src/textual/renderables/text_opacity.py index dffb5667bb..638a3458bc 100644 --- a/src/textual/renderables/text_opacity.py +++ b/src/textual/renderables/text_opacity.py @@ -1,5 +1,5 @@ import functools -from typing import Iterable +from typing import Iterable, Iterator from rich.cells import cell_len from rich.color import Color @@ -63,6 +63,7 @@ def process_segments( _from_color = Style.from_color if opacity == 0: for text, style, control in segments: + assert style is not None invisible_style = _from_color(bgcolor=style.bgcolor) yield _Segment(cell_len(text) * " ", invisible_style) else: @@ -106,7 +107,7 @@ def __rich_console__( opacity_panel = TextOpacity(panel, opacity=0.5) console.print(opacity_panel) - def frange(start, end, step): + def frange(start: float, end: float, step: float) -> Iterator[float]: current = start while current < end: yield current @@ -120,5 +121,5 @@ def frange(start, end, step): with Live(opacity_panel, refresh_per_second=60) as live: for value in itertools.cycle(frange(0, 1, 0.05)): - opacity_panel.value = value + opacity_panel.opacity = value sleep(0.05) diff --git a/src/textual/strip.py b/src/textual/strip.py index 7a83d5e40a..5a4c1404e2 100644 --- a/src/textual/strip.py +++ b/src/textual/strip.py @@ -109,8 +109,8 @@ def __reversed__(self) -> Iterator[Segment]: def __len__(self) -> int: return len(self._segments) - def __eq__(self, strip: Strip) -> bool: - return ( + def __eq__(self, strip: object) -> bool: + return isinstance(strip, Strip) and ( self._segments == strip._segments and self.cell_length == strip.cell_length ) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index e23385502c..d0d7622f2e 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -31,12 +31,12 @@ @dataclass -class _TreeLine: - path: list[TreeNode] +class _TreeLine(Generic[TreeDataType]): + path: list[TreeNode[TreeDataType]] last: bool @property - def node(self) -> TreeNode: + def node(self) -> TreeNode[TreeDataType]: """TreeNode: The node associated with this line.""" return self.path[-1] @@ -74,7 +74,7 @@ def __init__( self._label = label self.data = data self._expanded = expanded - self._children: list[TreeNode] = [] + self._children: list[TreeNode[TreeDataType]] = [] self._hover_ = False self._selected_ = False @@ -466,11 +466,11 @@ def clear(self) -> None: self._updates += 1 self.refresh() - def select_node(self, node: TreeNode | None) -> None: + def select_node(self, node: TreeNode[TreeDataType] | None) -> None: """Move the cursor to the given node, or reset cursor. Args: - node (TreeNode | None): A tree node, or None to reset cursor. + node (TreeNode[TreeDataType] | None): A tree node, or None to reset cursor. """ self.cursor_line = -1 if node is None else node._line @@ -570,11 +570,11 @@ def scroll_to_line(self, line: int) -> None: """ self.scroll_to_region(Region(0, line, self.size.width, 1)) - def scroll_to_node(self, node: TreeNode) -> None: + def scroll_to_node(self, node: TreeNode[TreeDataType]) -> None: """Scroll to the given node. Args: - node (TreeNode): Node to scroll in to view. + node (TreeNode[TreeDataType]): Node to scroll in to view. """ line = node._line if line != -1: @@ -628,7 +628,7 @@ def _build(self) -> None: root = self.root - def add_node(path: list[TreeNode], node: TreeNode, last: bool) -> None: + def add_node(path: list[TreeNode[TreeDataType]], node: TreeNode[TreeDataType], last: bool) -> None: child_path = [*path, node] node._line = len(lines) add_line(TreeLine(child_path, last)) From 080687ec609e1588bc3df1acb2c7bba3cef325aa Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Mon, 2 Jan 2023 21:05:12 +0200 Subject: [PATCH 2/6] fix PR comments thus far --- src/textual/_node_list.py | 33 ++++++++------- src/textual/devtools/server.py | 4 -- src/textual/geometry.py | 2 - src/textual/pilot.py | 3 +- src/textual/renderables/sparkline.py | 4 +- src/textual/renderables/text_opacity.py | 54 +++++-------------------- 6 files changed, 29 insertions(+), 71 deletions(-) diff --git a/src/textual/_node_list.py b/src/textual/_node_list.py index 5a464405a6..1fe4d135f1 100644 --- a/src/textual/_node_list.py +++ b/src/textual/_node_list.py @@ -1,20 +1,19 @@ from __future__ import annotations import sys -from typing import TYPE_CHECKING, Any, Iterator, Sequence, overload +from typing import Any, Hashable, Iterator, Sequence, TypeVar, overload import rich.repr -if TYPE_CHECKING: - from .widget import Widget - class DuplicateIds(Exception): pass +_T = TypeVar("_T", bound=Hashable) + @rich.repr.auto(angular=True) -class NodeList(Sequence[Widget]): +class NodeList(Sequence[_T]): """ A container for widgets that forms one level of hierarchy. @@ -24,13 +23,13 @@ class NodeList(Sequence[Widget]): def __init__(self) -> None: # The nodes in the list - self._nodes: list[Widget] = [] - self._nodes_set: set[Widget] = set() + self._nodes: list[_T] = [] + self._nodes_set: set[_T] = set() # We cache widgets by their IDs too for a quick lookup # Note that only widgets with IDs are cached like this, so # this cache will likely hold fewer values than self._nodes. - self._nodes_by_id: dict[str, Widget] = {} + self._nodes_by_id: dict[str, _T] = {} # Increments when list is updated (used for caching) self._updates = 0 @@ -64,11 +63,11 @@ def index(self, widget: Any, start: int = 0, stop: int = sys.maxsize) -> int: """ return self._nodes.index(widget, start, stop) - def _get_by_id(self, widget_id: str) -> Widget | None: + def _get_by_id(self, widget_id: str) -> _T | None: """Get the widget for the given widget_id, or None if there's no matches in this list""" return self._nodes_by_id.get(widget_id) - def _append(self, widget: Widget) -> None: + def _append(self, widget: _T) -> None: """Append a Widget. Args: @@ -83,7 +82,7 @@ def _append(self, widget: Widget) -> None: self._nodes_by_id[widget_id] = widget self._updates += 1 - def _insert(self, index: int, widget: Widget) -> None: + def _insert(self, index: int, widget: _T) -> None: """Insert a Widget. Args: @@ -106,7 +105,7 @@ def _ensure_unique_id(self, widget_id: str) -> None: "The children of a widget must have unique IDs." ) - def _remove(self, widget: Widget) -> None: + def _remove(self, widget: _T) -> None: """Remove a widget from the list. Removing a widget not in the list is a null-op. @@ -130,19 +129,19 @@ def _clear(self) -> None: self._nodes_by_id.clear() self._updates += 1 - def __iter__(self) -> Iterator[Widget]: + def __iter__(self) -> Iterator[_T]: return iter(self._nodes) - def __reversed__(self) -> Iterator[Widget]: + def __reversed__(self) -> Iterator[_T]: return reversed(self._nodes) @overload - def __getitem__(self, index: int) -> Widget: + def __getitem__(self, index: int) -> _T: ... @overload - def __getitem__(self, index: slice) -> list[Widget]: + def __getitem__(self, index: slice) -> list[_T]: ... - def __getitem__(self, index: int | slice) -> Widget | list[Widget]: + def __getitem__(self, index: int | slice) -> _T | list[_T]: return self._nodes[index] diff --git a/src/textual/devtools/server.py b/src/textual/devtools/server.py index 3e32f628fd..82e3fababb 100644 --- a/src/textual/devtools/server.py +++ b/src/textual/devtools/server.py @@ -77,7 +77,3 @@ def _make_devtools_aiohttp_app( ) return app - - -if __name__ == "__main__": - _run_devtools(True) diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 2dbb2c22ae..220959dfe5 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -219,8 +219,6 @@ def contains_point(self, point: tuple[int, int]) -> bool: def __contains__(self, other: Any) -> bool: try: x, y = other - assert isinstance(x, int) - assert isinstance(y, int) except Exception: raise TypeError( "Dimensions.__contains__ requires an iterable of two integers" diff --git a/src/textual/pilot.py b/src/textual/pilot.py index d13d1bb8d3..1e58822b46 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -5,8 +5,7 @@ import asyncio from typing import TYPE_CHECKING, Generic -if TYPE_CHECKING: - from .app import App, ReturnType +from .app import App, ReturnType @rich.repr.auto(angular=True) diff --git a/src/textual/renderables/sparkline.py b/src/textual/renderables/sparkline.py index eb55a6d56b..17ca5a3476 100644 --- a/src/textual/renderables/sparkline.py +++ b/src/textual/renderables/sparkline.py @@ -76,9 +76,9 @@ def __rich_console__( buckets = tuple(self._buckets(self.data, num_buckets=width)) - bucket_index = 0 + bucket_index = 0.0 bars_rendered = 0 - step = len(buckets) // width + step = len(buckets) / width summary_function = self.summary_function min_color, max_color = self.min_color.color, self.max_color.color assert min_color is not None diff --git a/src/textual/renderables/text_opacity.py b/src/textual/renderables/text_opacity.py index 638a3458bc..9f62378794 100644 --- a/src/textual/renderables/text_opacity.py +++ b/src/textual/renderables/text_opacity.py @@ -1,5 +1,5 @@ import functools -from typing import Iterable, Iterator +from typing import Iterable, cast from rich.cells import cell_len from rich.color import Color @@ -62,22 +62,25 @@ def process_segments( _Segment = Segment _from_color = Style.from_color if opacity == 0: - for text, style, control in segments: + for text, style, control in cast( + Iterable[tuple[str, Style, object]], + segments + ): assert style is not None invisible_style = _from_color(bgcolor=style.bgcolor) yield _Segment(cell_len(text) * " ", invisible_style) else: for segment in segments: - text, style, control = segment - if not style: + text, maybe_style, control = segment + if not maybe_style: yield segment continue - color = style.color - bgcolor = style.bgcolor + color = maybe_style.color + bgcolor = maybe_style.bgcolor if color and color.triplet and bgcolor and bgcolor.triplet: color_style = _get_blended_style_cached(bgcolor, color, opacity) - yield _Segment(text, style + color_style) + yield _Segment(text, maybe_style + color_style) else: yield segment @@ -86,40 +89,3 @@ def __rich_console__( ) -> RenderResult: segments = console.render(self.renderable, options) return self.process_segments(segments, self.opacity) - - -if __name__ == "__main__": - from rich.live import Live - from rich.panel import Panel - from rich.text import Text - - from time import sleep - - console = Console() - - panel = Panel.fit( - Text("Steak: £30", style="#fcffde on #03761e"), - title="Menu", - style="#ffffff on #000000", - ) - console.print(panel) - - opacity_panel = TextOpacity(panel, opacity=0.5) - console.print(opacity_panel) - - def frange(start: float, end: float, step: float) -> Iterator[float]: - current = start - while current < end: - yield current - current += step - - while current >= 0: - yield current - current -= step - - import itertools - - with Live(opacity_panel, refresh_per_second=60) as live: - for value in itertools.cycle(frange(0, 1, 0.05)): - opacity_panel.opacity = value - sleep(0.05) From 5750666ad8e91f48504ee47aa0eb64b64d39c394 Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Mon, 2 Jan 2023 21:50:14 +0200 Subject: [PATCH 3/6] further PR fixes --- src/textual/_node_list.py | 33 +++++++++++++++++---------------- src/textual/pilot.py | 2 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/textual/_node_list.py b/src/textual/_node_list.py index 1fe4d135f1..6e7a4fba1d 100644 --- a/src/textual/_node_list.py +++ b/src/textual/_node_list.py @@ -1,19 +1,20 @@ from __future__ import annotations import sys -from typing import Any, Hashable, Iterator, Sequence, TypeVar, overload +from typing import TYPE_CHECKING, Any, Iterator, Sequence, overload import rich.repr +if TYPE_CHECKING: + from .widget import Widget + class DuplicateIds(Exception): pass -_T = TypeVar("_T", bound=Hashable) - @rich.repr.auto(angular=True) -class NodeList(Sequence[_T]): +class NodeList(Sequence["Widget"]): """ A container for widgets that forms one level of hierarchy. @@ -23,13 +24,13 @@ class NodeList(Sequence[_T]): def __init__(self) -> None: # The nodes in the list - self._nodes: list[_T] = [] - self._nodes_set: set[_T] = set() + self._nodes: list[Widget] = [] + self._nodes_set: set[Widget] = set() # We cache widgets by their IDs too for a quick lookup # Note that only widgets with IDs are cached like this, so # this cache will likely hold fewer values than self._nodes. - self._nodes_by_id: dict[str, _T] = {} + self._nodes_by_id: dict[str, Widget] = {} # Increments when list is updated (used for caching) self._updates = 0 @@ -63,11 +64,11 @@ def index(self, widget: Any, start: int = 0, stop: int = sys.maxsize) -> int: """ return self._nodes.index(widget, start, stop) - def _get_by_id(self, widget_id: str) -> _T | None: + def _get_by_id(self, widget_id: str) -> Widget | None: """Get the widget for the given widget_id, or None if there's no matches in this list""" return self._nodes_by_id.get(widget_id) - def _append(self, widget: _T) -> None: + def _append(self, widget: Widget) -> None: """Append a Widget. Args: @@ -82,7 +83,7 @@ def _append(self, widget: _T) -> None: self._nodes_by_id[widget_id] = widget self._updates += 1 - def _insert(self, index: int, widget: _T) -> None: + def _insert(self, index: int, widget: Widget) -> None: """Insert a Widget. Args: @@ -105,7 +106,7 @@ def _ensure_unique_id(self, widget_id: str) -> None: "The children of a widget must have unique IDs." ) - def _remove(self, widget: _T) -> None: + def _remove(self, widget: Widget) -> None: """Remove a widget from the list. Removing a widget not in the list is a null-op. @@ -129,19 +130,19 @@ def _clear(self) -> None: self._nodes_by_id.clear() self._updates += 1 - def __iter__(self) -> Iterator[_T]: + def __iter__(self) -> Iterator[Widget]: return iter(self._nodes) - def __reversed__(self) -> Iterator[_T]: + def __reversed__(self) -> Iterator[Widget]: return reversed(self._nodes) @overload - def __getitem__(self, index: int) -> _T: + def __getitem__(self, index: int) -> Widget: ... @overload - def __getitem__(self, index: slice) -> list[_T]: + def __getitem__(self, index: slice) -> list[Widget]: ... - def __getitem__(self, index: int | slice) -> _T | list[_T]: + def __getitem__(self, index: int | slice) -> Widget | list[Widget]: return self._nodes[index] diff --git a/src/textual/pilot.py b/src/textual/pilot.py index 1e58822b46..ed118a2dc0 100644 --- a/src/textual/pilot.py +++ b/src/textual/pilot.py @@ -3,7 +3,7 @@ import rich.repr import asyncio -from typing import TYPE_CHECKING, Generic +from typing import Generic from .app import App, ReturnType From c1a90c25a14c0e9b78551bfa5a4cc1a6ed339f9f Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Mon, 2 Jan 2023 21:58:23 +0200 Subject: [PATCH 4/6] black --- src/textual/renderables/sparkline.py | 9 ++++++++- src/textual/renderables/text_opacity.py | 3 +-- src/textual/widgets/_tree.py | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/textual/renderables/sparkline.py b/src/textual/renderables/sparkline.py index 17ca5a3476..755c12440a 100644 --- a/src/textual/renderables/sparkline.py +++ b/src/textual/renderables/sparkline.py @@ -14,6 +14,7 @@ SummaryFunction = Callable[[Sequence[T]], float] + class Sparkline(Generic[T]): """A sparkline representing a series of data. @@ -100,7 +101,13 @@ def __rich_console__( def last(l: Sequence[T]) -> T: return l[-1] - funcs: Sequence[SummaryFunction[int]] = (min, max, last, statistics.median, statistics.mean) + funcs: Sequence[SummaryFunction[int]] = ( + min, + max, + last, + statistics.median, + statistics.mean, + ) nums = [10, 2, 30, 60, 45, 20, 7, 8, 9, 10, 50, 13, 10, 6, 5, 4, 3, 7, 20] console.print(f"data = {nums}\n") for f in funcs: diff --git a/src/textual/renderables/text_opacity.py b/src/textual/renderables/text_opacity.py index 9f62378794..a0ba1c2228 100644 --- a/src/textual/renderables/text_opacity.py +++ b/src/textual/renderables/text_opacity.py @@ -63,8 +63,7 @@ def process_segments( _from_color = Style.from_color if opacity == 0: for text, style, control in cast( - Iterable[tuple[str, Style, object]], - segments + Iterable[tuple[str, Style, object]], segments ): assert style is not None invisible_style = _from_color(bgcolor=style.bgcolor) diff --git a/src/textual/widgets/_tree.py b/src/textual/widgets/_tree.py index d0d7622f2e..a96f40a16f 100644 --- a/src/textual/widgets/_tree.py +++ b/src/textual/widgets/_tree.py @@ -628,7 +628,9 @@ def _build(self) -> None: root = self.root - def add_node(path: list[TreeNode[TreeDataType]], node: TreeNode[TreeDataType], last: bool) -> None: + def add_node( + path: list[TreeNode[TreeDataType]], node: TreeNode[TreeDataType], last: bool + ) -> None: child_path = [*path, node] node._line = len(lines) add_line(TreeLine(child_path, last)) From b78ca2bad239f122ee1b8eedc9e322424c0110ea Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Tue, 3 Jan 2023 09:36:25 +0200 Subject: [PATCH 5/6] more PR fixes --- src/textual/geometry.py | 5 +++-- src/textual/renderables/text_opacity.py | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/textual/geometry.py b/src/textual/geometry.py index 220959dfe5..9b5c09f03c 100644 --- a/src/textual/geometry.py +++ b/src/textual/geometry.py @@ -7,7 +7,6 @@ from __future__ import annotations from functools import lru_cache -import math from operator import attrgetter, itemgetter from typing import Any, Collection, NamedTuple, Tuple, TypeVar, Union, cast @@ -130,7 +129,7 @@ def get_distance_to(self, other: Offset) -> float: """ x1, y1 = self x2, y2 = other - distance = math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) + distance: float = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) ** 0.5 return distance @@ -218,6 +217,8 @@ def contains_point(self, point: tuple[int, int]) -> bool: def __contains__(self, other: Any) -> bool: try: + x: int + y: int x, y = other except Exception: raise TypeError( diff --git a/src/textual/renderables/text_opacity.py b/src/textual/renderables/text_opacity.py index a0ba1c2228..2382ebd7f7 100644 --- a/src/textual/renderables/text_opacity.py +++ b/src/textual/renderables/text_opacity.py @@ -65,21 +65,20 @@ def process_segments( for text, style, control in cast( Iterable[tuple[str, Style, object]], segments ): - assert style is not None invisible_style = _from_color(bgcolor=style.bgcolor) yield _Segment(cell_len(text) * " ", invisible_style) else: for segment in segments: - text, maybe_style, control = segment - if not maybe_style: + text, style, control = cast(tuple[str, Style, object], segment) + if not style: yield segment continue - color = maybe_style.color - bgcolor = maybe_style.bgcolor + color = style.color + bgcolor = style.bgcolor if color and color.triplet and bgcolor and bgcolor.triplet: color_style = _get_blended_style_cached(bgcolor, color, opacity) - yield _Segment(text, maybe_style + color_style) + yield _Segment(text, style + color_style) else: yield segment From 6e9d302e150902f8dd4d8ffbab7645f2a5aea0f3 Mon Sep 17 00:00:00 2001 From: Nitzan Shaked Date: Thu, 5 Jan 2023 14:23:40 +0200 Subject: [PATCH 6/6] fix typing of tuple[] for py3.7 --- src/textual/renderables/text_opacity.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/textual/renderables/text_opacity.py b/src/textual/renderables/text_opacity.py index 2382ebd7f7..fdbec1a16e 100644 --- a/src/textual/renderables/text_opacity.py +++ b/src/textual/renderables/text_opacity.py @@ -1,5 +1,5 @@ import functools -from typing import Iterable, cast +from typing import Iterable, Tuple, cast from rich.cells import cell_len from rich.color import Color @@ -63,13 +63,16 @@ def process_segments( _from_color = Style.from_color if opacity == 0: for text, style, control in cast( - Iterable[tuple[str, Style, object]], segments + # use Tuple rather than tuple so Python 3.7 doesn't complain + Iterable[Tuple[str, Style, object]], + segments, ): invisible_style = _from_color(bgcolor=style.bgcolor) yield _Segment(cell_len(text) * " ", invisible_style) else: for segment in segments: - text, style, control = cast(tuple[str, Style, object], segment) + # use Tuple rather than tuple so Python 3.7 doesn't complain + text, style, control = cast(Tuple[str, Style, object], segment) if not style: yield segment continue