Repository: Textualize/textual

Issue:
Sparklines do not work with collections.deque
```python
from collections import deque

from textual.app import App, ComposeResult
from textual.widgets import Sparkline

data = deque([1, 2, 2, 1, 1, 4, 3, 1, 1, 8, 8, 2])


class SparklineBasicApp(App[None]):
    def compose(self) -> ComposeResult:
        yield Sparkline(
            data,
            summary_function=max,
        )


app = SparklineBasicApp()
if __name__ == "__main__":
    app.run()
```
I was working on creating a scrolling Sparkline and ran into this problem. When using a deque as the `data` element for a Sparkline you receive a TypeError: sequence index must be integer, not 'slice' exception.


Discussion:
Hey, thanks for raising this issue.

As a temporary workaround, which you probably thought of already, you can create the sparkline with `Sparkline(list(data), ...)`.
Seems to be a typing issue. A Sequence should accept a slice according to the [docs](https://docs.python.org/3/reference/datamodel.html#object.__getitem__).
In conclusion, `issubclass(deque, Sequence)` should return `False`, right?
In that case, we can close this issue, right?
> Hey, thanks for raising this issue.
> 
> As a temporary workaround, which you probably thought of already, you can create the sparkline with `Sparkline(list(data), ...)`.

I, of course, had not already thought of that, though I would have gotten there eventually! Thank you very much.
Opened the bug report https://github.com/python/cpython/issues/113313 to track the underlying issue.
So it looks like the Python devs have refined the definition of Sequence to not include slices.

We should look in to updating the typing, or make it work without slices if at all possible.

Resolving patch:
diff --git a/src/textual/renderables/sparkline.py b/src/textual/renderables/sparkline.py
--- a/src/textual/renderables/sparkline.py
+++ b/src/textual/renderables/sparkline.py
@@ -45,7 +45,7 @@ def __init__(
         self.summary_function: SummaryFunction[T] = summary_function
 
     @classmethod
-    def _buckets(cls, data: Sequence[T], num_buckets: int) -> Iterable[Sequence[T]]:
+    def _buckets(cls, data: list[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]].
 
@@ -76,7 +76,7 @@ def __rich_console__(
         minimum, maximum = min(self.data), max(self.data)
         extent = maximum - minimum or 1
 
-        buckets = tuple(self._buckets(self.data, num_buckets=width))
+        buckets = tuple(self._buckets(list(self.data), num_buckets=width))
 
         bucket_index = 0.0
         bars_rendered = 0


Test patch:
diff --git a/tests/renderables/test_sparkline.py b/tests/renderables/test_sparkline.py
--- a/tests/renderables/test_sparkline.py
+++ b/tests/renderables/test_sparkline.py
@@ -1,3 +1,8 @@
+from collections import UserList, deque
+from typing import Sequence
+
+import pytest
+
 from tests.utilities.render import render
 from textual.renderables.sparkline import Sparkline
 
@@ -46,3 +51,24 @@ def test_sparkline_color_blend():
         render(Sparkline([1, 2, 3], width=3))
         == f"{GREEN}▁{STOP}{BLENDED}▄{STOP}{RED}█{STOP}"
     )
+
+
+@pytest.mark.parametrize(
+    "data",
+    [
+        (1, 2, 3),
+        [1, 2, 3],
+        bytearray((1, 2, 3)),
+        bytes((1, 2, 3)),
+        deque([1, 2, 3]),
+        range(1, 4),
+        UserList((1, 2, 3)),
+    ],
+)
+def test_sparkline_sequence_types(data: Sequence[int]):
+    """Sparkline should work with common Sequence types."""
+    assert issubclass(type(data), Sequence)
+    assert (
+        render(Sparkline(data, width=3))
+        == f"{GREEN}▁{STOP}{BLENDED}▄{STOP}{RED}█{STOP}"
+    )