Repository: strawberry-graphql/strawberry

Issue:
Generic class implementing a `strawberry.interface` raises error
Congrats on this awesome package, I've been using it for the past 2 weeks and really enjoy it! In my present usage I've run into 
an issue with interfaces that are implemented by generics. This is demonstrated by the minimal example below:

````python
from typing import Any, Generic, TypeVar

import strawberry


@strawberry.interface
class ObjectType:

    obj: strawberry.Private[Any]

    @strawberry.field
    def repr(self) -> str:
        return str(self.obj)


T = TypeVar("T")


@strawberry.type
class GenericObject(ObjectType, Generic[T]):
    @strawberry.field
    def value(self) -> T:
        return self.obj


@strawberry.type
class Query:
    @strawberry.field
    def foo(self) -> GenericObject[str]:
        return GenericObject("foo")


schema = strawberry.Schema(query=Query)
query_result = schema.execute_sync(
    """
        query {
            foo {
                __typename
                value
                repr
            }
        }
    """
)
print(query_result)


````

Will raise the following error:

````bash
graphql.error.graphql_error.GraphQLError: Expected value of type 'StrGenericObject' but got: <GenericObject instance>.

GraphQL request:3:13
2 |         query {
3 |             foo {
  |             ^
4 |                 __typename
````

However, changing the use of `strawberry.interface` to `strawberry.type` as follows:
```python
@strawberry.type
class ObjectType:
    ...
```

Will return the expected result:

```bash
ExecutionResult(data={'foo': {'__typename': 'StrGenericObject', 'value': 'foo', 'repr': 'foo'}}, errors=None, extensions={})
```


Discussion:
I can reproduce this 😊 I'll see if I can fix it this week

Resolving patch:
diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py
--- a/strawberry/schema/schema_converter.py
+++ b/strawberry/schema/schema_converter.py
@@ -285,23 +285,30 @@ def from_object(self, object_type: TypeDefinition) -> GraphQLObjectType:
             assert isinstance(graphql_object_type, GraphQLObjectType)  # For mypy
             return graphql_object_type
 
-        is_type_of: Optional[Callable[[Any, GraphQLResolveInfo], bool]]
-        if object_type.is_type_of:
-            is_type_of = object_type.is_type_of
-        elif object_type.interfaces:
+        def _get_is_type_of() -> Optional[Callable[[Any, GraphQLResolveInfo], bool]]:
+            if object_type.is_type_of:
+                return object_type.is_type_of
+
+            if not object_type.interfaces:
+                return None
 
             def is_type_of(obj: Any, _info: GraphQLResolveInfo) -> bool:
+                if object_type.concrete_of and (
+                    hasattr(obj, "_type_definition")
+                    and obj._type_definition.origin is object_type.concrete_of.origin
+                ):
+                    return True
+
                 return isinstance(obj, object_type.origin)
 
-        else:
-            is_type_of = None
+            return is_type_of
 
         graphql_object_type = GraphQLObjectType(
             name=object_type_name,
             fields=lambda: self.get_graphql_fields(object_type),
             interfaces=list(map(self.from_interface, object_type.interfaces)),
             description=object_type.description,
-            is_type_of=is_type_of,
+            is_type_of=_get_is_type_of(),
         )
 
         self.type_map[object_type_name] = ConcreteType(
@@ -512,3 +519,25 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType:
         )
 
         return graphql_union
+
+    def _get_is_type_of(
+        self,
+        object_type: TypeDefinition,
+    ) -> Optional[Callable[[Any, GraphQLResolveInfo], bool]]:
+        if object_type.is_type_of:
+            return object_type.is_type_of
+
+        if object_type.interfaces:
+
+            def is_type_of(obj: Any, _info: GraphQLResolveInfo) -> bool:
+                if object_type.concrete_of and (
+                    hasattr(obj, "_type_definition")
+                    and obj._type_definition.origin is object_type.concrete_of.origin
+                ):
+                    return True
+
+                return isinstance(obj, object_type.origin)
+
+            return is_type_of
+
+        return None


Test patch:
diff --git a/tests/schema/test_generics.py b/tests/schema/test_generics.py
--- a/tests/schema/test_generics.py
+++ b/tests/schema/test_generics.py
@@ -1,6 +1,6 @@
 import textwrap
-import typing
 from enum import Enum
+from typing import Any, Generic, List, Optional, TypeVar, Union
 
 import pytest
 
@@ -8,10 +8,10 @@
 
 
 def test_supports_generic_simple_type():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         cursor: strawberry.ID
         node_field: T
 
@@ -40,10 +40,10 @@ def example(self) -> Edge[int]:
 
 
 def test_supports_generic():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         cursor: strawberry.ID
         node: T
 
@@ -82,11 +82,11 @@ def example(self) -> Edge[Person]:
 
 
 def test_supports_multiple_generic():
-    A = typing.TypeVar("A")
-    B = typing.TypeVar("B")
+    A = TypeVar("A")
+    B = TypeVar("B")
 
     @strawberry.type
-    class Multiple(typing.Generic[A, B]):
+    class Multiple(Generic[A, B]):
         a: A
         b: B
 
@@ -115,18 +115,18 @@ def multiple(self) -> Multiple[int, str]:
 
 
 def test_support_nested_generics():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         node: T
 
     @strawberry.type
-    class Connection(typing.Generic[T]):
+    class Connection(Generic[T]):
         edge: Edge[T]
 
     @strawberry.type
@@ -161,15 +161,15 @@ def users(self) -> Connection[User]:
 
 
 def test_supports_optional():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        node: typing.Optional[T] = None
+    class Edge(Generic[T]):
+        node: Optional[T] = None
 
     @strawberry.type
     class Query:
@@ -195,15 +195,15 @@ def user(self) -> Edge[User]:
 
 
 def test_supports_lists():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        nodes: typing.List[T]
+    class Edge(Generic[T]):
+        nodes: List[T]
 
     @strawberry.type
     class Query:
@@ -229,15 +229,15 @@ def user(self) -> Edge[User]:
 
 
 def test_supports_lists_of_optionals():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        nodes: typing.List[typing.Optional[T]]
+    class Edge(Generic[T]):
+        nodes: List[Optional[T]]
 
     @strawberry.type
     class Query:
@@ -263,19 +263,19 @@ def user(self) -> Edge[User]:
 
 
 def test_can_extend_generics():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         node: T
 
     @strawberry.type
-    class Connection(typing.Generic[T]):
-        edges: typing.List[Edge[T]]
+    class Connection(Generic[T]):
+        edges: List[Edge[T]]
 
     @strawberry.type
     class ConnectionWithMeta(Connection[T]):
@@ -315,10 +315,10 @@ def users(self) -> ConnectionWithMeta[User]:
 
 
 def test_supports_generic_in_unions():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         cursor: strawberry.ID
         node: T
 
@@ -329,7 +329,7 @@ class Fallback:
     @strawberry.type
     class Query:
         @strawberry.field
-        def example(self) -> typing.Union[Fallback, Edge[int]]:
+        def example(self) -> Union[Fallback, Edge[int]]:
             return Edge(cursor=strawberry.ID("1"), node=1)
 
     schema = strawberry.Schema(query=Query)
@@ -354,14 +354,14 @@ def example(self) -> typing.Union[Fallback, Edge[int]]:
 
 
 def test_generic_with_enum_as_param_of_type_inside_unions():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class Pet:
         name: str
 
     @strawberry.type
-    class ErrorNode(typing.Generic[T]):
+    class ErrorNode(Generic[T]):
         code: T
 
     @strawberry.enum
@@ -372,7 +372,7 @@ class Codes(Enum):
     @strawberry.type
     class Query:
         @strawberry.field
-        def result(self) -> typing.Union[Pet, ErrorNode[Codes]]:
+        def result(self) -> Union[Pet, ErrorNode[Codes]]:
             return ErrorNode(code=Codes.a)
 
     schema = strawberry.Schema(query=Query)
@@ -393,11 +393,11 @@ def result(self) -> typing.Union[Pet, ErrorNode[Codes]]:
 
 
 def test_supports_generic_in_unions_multiple_vars():
-    A = typing.TypeVar("A")
-    B = typing.TypeVar("B")
+    A = TypeVar("A")
+    B = TypeVar("B")
 
     @strawberry.type
-    class Edge(typing.Generic[A, B]):
+    class Edge(Generic[A, B]):
         info: A
         node: B
 
@@ -408,7 +408,7 @@ class Fallback:
     @strawberry.type
     class Query:
         @strawberry.field
-        def example(self) -> typing.Union[Fallback, Edge[int, str]]:
+        def example(self) -> Union[Fallback, Edge[int, str]]:
             return Edge(node="string", info=1)
 
     schema = strawberry.Schema(query=Query)
@@ -433,18 +433,18 @@ def example(self) -> typing.Union[Fallback, Edge[int, str]]:
 
 
 def test_supports_generic_in_unions_with_nesting():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         node: T
 
     @strawberry.type
-    class Connection(typing.Generic[T]):
+    class Connection(Generic[T]):
         edge: Edge[T]
 
     @strawberry.type
@@ -454,7 +454,7 @@ class Fallback:
     @strawberry.type
     class Query:
         @strawberry.field
-        def users(self) -> typing.Union[Connection[User], Fallback]:
+        def users(self) -> Union[Connection[User], Fallback]:
             return Connection(edge=Edge(node=User("Patrick")))
 
     schema = strawberry.Schema(query=Query)
@@ -485,17 +485,17 @@ def users(self) -> typing.Union[Connection[User], Fallback]:
 
 
 def test_supports_multiple_generics_in_union():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
+    class Edge(Generic[T]):
         cursor: strawberry.ID
         node: T
 
     @strawberry.type
     class Query:
         @strawberry.field
-        def example(self) -> typing.List[typing.Union[Edge[int], Edge[str]]]:
+        def example(self) -> List[Union[Edge[int], Edge[str]]]:
             return [
                 Edge(cursor=strawberry.ID("1"), node=1),
                 Edge(cursor=strawberry.ID("2"), node="string"),
@@ -551,10 +551,10 @@ def example(self) -> typing.List[typing.Union[Edge[int], Edge[str]]]:
 
 
 def test_generated_names():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class EdgeWithCursor(typing.Generic[T]):
+    class EdgeWithCursor(Generic[T]):
         cursor: strawberry.ID
         node: T
 
@@ -595,20 +595,20 @@ def person_edge(self) -> EdgeWithCursor[SpecialPerson]:
 
 
 def test_supports_lists_within_unions():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        nodes: typing.List[T]
+    class Edge(Generic[T]):
+        nodes: List[T]
 
     @strawberry.type
     class Query:
         @strawberry.field
-        def user(self) -> typing.Union[User, Edge[User]]:
+        def user(self) -> Union[User, Edge[User]]:
             return Edge(nodes=[User("P")])
 
     schema = strawberry.Schema(query=Query)
@@ -632,20 +632,20 @@ def user(self) -> typing.Union[User, Edge[User]]:
 
 
 def test_supports_lists_within_unions_empty_list():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        nodes: typing.List[T]
+    class Edge(Generic[T]):
+        nodes: List[T]
 
     @strawberry.type
     class Query:
         @strawberry.field
-        def user(self) -> typing.Union[User, Edge[User]]:
+        def user(self) -> Union[User, Edge[User]]:
             return Edge(nodes=[])
 
     schema = strawberry.Schema(query=Query)
@@ -670,20 +670,20 @@ def user(self) -> typing.Union[User, Edge[User]]:
 
 @pytest.mark.xfail()
 def test_raises_error_when_unable_to_find_type():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
     class User:
         name: str
 
     @strawberry.type
-    class Edge(typing.Generic[T]):
-        nodes: typing.List[T]
+    class Edge(Generic[T]):
+        nodes: List[T]
 
     @strawberry.type
     class Query:
         @strawberry.field
-        def user(self) -> typing.Union[User, Edge[User]]:
+        def user(self) -> Union[User, Edge[User]]:
             return Edge(nodes=["bad example"])  # type: ignore
 
     schema = strawberry.Schema(query=Query)
@@ -710,12 +710,12 @@ def user(self) -> typing.Union[User, Edge[User]]:
 
 
 def test_generic_with_arguments():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.type
-    class Collection(typing.Generic[T]):
+    class Collection(Generic[T]):
         @strawberry.field
-        def by_id(self, ids: typing.List[int]) -> typing.List[T]:
+        def by_id(self, ids: List[int]) -> List[T]:
             return []
 
     @strawberry.type
@@ -746,13 +746,13 @@ class Query:
 
 
 def test_generic_extending_with_type_var():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.interface
-    class Node(typing.Generic[T]):
+    class Node(Generic[T]):
         id: strawberry.ID
 
-        def _resolve(self) -> typing.Optional[T]:
+        def _resolve(self) -> Optional[T]:
             return None
 
     @strawberry.type
@@ -762,7 +762,7 @@ class Book(Node[str]):
     @strawberry.type
     class Query:
         @strawberry.field
-        def books(self) -> typing.List[Book]:
+        def books(self) -> List[Book]:
             return list()
 
     schema = strawberry.Schema(query=Query)
@@ -786,10 +786,10 @@ def books(self) -> typing.List[Book]:
 
 
 def test_supports_generic_input_type():
-    T = typing.TypeVar("T")
+    T = TypeVar("T")
 
     @strawberry.input
-    class Input(typing.Generic[T]):
+    class Input(Generic[T]):
         field: T
 
     @strawberry.type
@@ -808,3 +808,50 @@ def field(self, input: Input[str]) -> str:
 
     assert not result.errors
     assert result.data == {"field": "data"}
+
+
+def test_generic_interface():
+    @strawberry.interface
+    class ObjectType:
+
+        obj: strawberry.Private[Any]
+
+        @strawberry.field
+        def repr(self) -> str:
+            return str(self.obj)
+
+    T = TypeVar("T")
+
+    @strawberry.type
+    class GenericObject(ObjectType, Generic[T]):
+        @strawberry.field
+        def value(self) -> T:
+            return self.obj
+
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def foo(self) -> GenericObject[str]:
+            return GenericObject("foo")
+
+    schema = strawberry.Schema(query=Query)
+    query_result = schema.execute_sync(
+        """
+            query {
+                foo {
+                    __typename
+                    value
+                    repr
+                }
+            }
+        """
+    )
+
+    assert not query_result.errors
+    assert query_result.data == {
+        "foo": {
+            "__typename": "StrGenericObject",
+            "value": "foo",
+            "repr": "foo",
+        }
+    }