Repository: strawberry-graphql/strawberry

Issue:
Allow using tuple (and/or iterable) as an alias to the GraphQL type
## Feature Request Type

Alteration (enhancement/optimization) of existing feature(s)

## Description

Returning a `tuple` when the attribute is hinted as `list` [works](https://play.strawberry.rocks/?gist=0815d1a1f0c58a613bd356cbeb45c8a1).

But hinting the return type _correctly as a tuple_ causes an error:

```python
@strawberry.type
class Dictionary:
    entries: tuple[Entry]
    sources: tuple[Source]
```

```
TypeError: Unexpected type tuple[Entry]
```

Our code uses tuples and iterables whenever appropriate for robustness, efficiency, and documentation. We also use strict type hinting. It'd be great for these read-only sequences to be explicitly supported.


Discussion:
I think that a more general `Sequence` annotation makes sense.

Resolving patch:
diff --git a/strawberry/annotation.py b/strawberry/annotation.py
--- a/strawberry/annotation.py
+++ b/strawberry/annotation.py
@@ -241,7 +241,11 @@ def _is_list(cls, annotation: Any) -> bool:
 
         annotation_origin = getattr(annotation, "__origin__", None)
 
-        return annotation_origin == list
+        return (
+            annotation_origin == list
+            or annotation_origin == tuple
+            or annotation_origin is abc.Sequence
+        )
 
     @classmethod
     def _is_strawberry_type(cls, evaled_type: Any) -> bool:


Test patch:
diff --git a/tests/types/resolving/test_lists.py b/tests/types/resolving/test_lists.py
--- a/tests/types/resolving/test_lists.py
+++ b/tests/types/resolving/test_lists.py
@@ -1,5 +1,6 @@
 import sys
-from typing import List, Optional, Union
+from collections.abc import Sequence
+from typing import List, Optional, Tuple, Union
 
 import pytest
 
@@ -19,6 +20,32 @@ def test_basic_list():
     assert resolved == List[str]
 
 
+def test_basic_tuple():
+    annotation = StrawberryAnnotation(Tuple[str])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type is str
+
+    assert resolved == StrawberryList(of_type=str)
+    assert resolved == Tuple[str]
+
+
+@pytest.mark.skipif(
+    sys.version_info < (3, 9),
+    reason="collections.abc.Sequence supporting [] was added in python 3.9",
+)
+def test_basic_sequence():
+    annotation = StrawberryAnnotation(Sequence[str])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type is str
+
+    assert resolved == StrawberryList(of_type=str)
+    assert resolved == Sequence[str]
+
+
 def test_list_of_optional():
     annotation = StrawberryAnnotation(List[Optional[int]])
     resolved = annotation.resolve()
@@ -30,6 +57,32 @@ def test_list_of_optional():
     assert resolved == List[Optional[int]]
 
 
+@pytest.mark.skipif(
+    sys.version_info < (3, 9),
+    reason="collections.abc.Sequence supporting [] was added in python 3.9",
+)
+def test_sequence_of_optional():
+    annotation = StrawberryAnnotation(Sequence[Optional[int]])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type == Optional[int]
+
+    assert resolved == StrawberryList(of_type=Optional[int])
+    assert resolved == Sequence[Optional[int]]
+
+
+def test_tuple_of_optional():
+    annotation = StrawberryAnnotation(Tuple[Optional[int]])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type == Optional[int]
+
+    assert resolved == StrawberryList(of_type=Optional[int])
+    assert resolved == Tuple[Optional[int]]
+
+
 def test_list_of_lists():
     annotation = StrawberryAnnotation(List[List[float]])
     resolved = annotation.resolve()
@@ -41,6 +94,32 @@ def test_list_of_lists():
     assert resolved == List[List[float]]
 
 
+@pytest.mark.skipif(
+    sys.version_info < (3, 9),
+    reason="collections.abc.Sequence supporting [] was added in python 3.9",
+)
+def test_sequence_of_sequence():
+    annotation = StrawberryAnnotation(Sequence[Sequence[float]])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type == Sequence[float]
+
+    assert resolved == StrawberryList(of_type=Sequence[float])
+    assert resolved == Sequence[Sequence[float]]
+
+
+def test_tuple_of_tuple():
+    annotation = StrawberryAnnotation(Tuple[Tuple[float]])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type == Tuple[float]
+
+    assert resolved == StrawberryList(of_type=Tuple[float])
+    assert resolved == Tuple[Tuple[float]]
+
+
 def test_list_of_union():
     @strawberry.type
     class Animal:
@@ -60,6 +139,29 @@ class Fungus:
     assert resolved == List[Union[Animal, Fungus]]
 
 
+@pytest.mark.skipif(
+    sys.version_info < (3, 9),
+    reason="collections.abc.Sequence supporting [] was added in python 3.9",
+)
+def test_sequence_of_union():
+    @strawberry.type
+    class Animal:
+        feet: bool
+
+    @strawberry.type
+    class Fungus:
+        spore: bool
+
+    annotation = StrawberryAnnotation(Sequence[Union[Animal, Fungus]])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type == Union[Animal, Fungus]
+
+    assert resolved == StrawberryList(of_type=Union[Animal, Fungus])
+    assert resolved == Sequence[Union[Animal, Fungus]]
+
+
 @pytest.mark.skipif(
     sys.version_info < (3, 9),
     reason="built-in generic annotations where added in python 3.9",
@@ -74,3 +176,19 @@ def test_list_builtin():
     assert resolved == StrawberryList(of_type=str)
     assert resolved == List[str]
     assert resolved == list[str]
+
+
+@pytest.mark.skipif(
+    sys.version_info < (3, 9),
+    reason="built-in generic annotations where added in python 3.9",
+)
+def test_tuple_builtin():
+    annotation = StrawberryAnnotation(tuple[str])
+    resolved = annotation.resolve()
+
+    assert isinstance(resolved, StrawberryList)
+    assert resolved.of_type is str
+
+    assert resolved == StrawberryList(of_type=str)
+    assert resolved == Tuple[str]
+    assert resolved == tuple[str]