Repository: strawberry-graphql/strawberry

Issue:
Broken duck typing when type implements an interface
This is currently broken (and it has been for a while apparently) due to how we handle `is_type_of`.

If we change it to none it works: https://github.com/strawberry-graphql/strawberry/blob/main/strawberry/schema/schema_converter.py#L265-L269

```python
from dataclasses import dataclass
from typing import List

import strawberry
from strawberry.types import Info


@strawberry.interface
class Node:
    id: int


@strawberry.type
class Anime(Node):
    anime_type: str


@dataclass
class AnimeORM:
    id: int
    anime_type: str


anime_list = [
    AnimeORM(
        id=1,
        anime_type="OVA",
    )
]


@strawberry.type
class Query:
    # @SAPaginatedField
    @strawberry.field
    def get_anime(self, info: Info) -> List[Anime]:
        return anime_list


schema = strawberry.Schema(Query)


print(
    schema.execute_sync(
        """{
            getAnime { id }
       }""",
        root_value=Query(),
    ).data,
)
```


Discussion:

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
@@ -222,11 +222,27 @@ def get_graphql_fields() -> Dict[str, GraphQLField]:
 
             return graphql_fields
 
+        def resolve_type(
+            obj: Any,
+            info: GraphQLResolveInfo,
+            type_: Union[GraphQLInterfaceType, GraphQLUnionType],
+        ) -> GraphQLObjectType:
+            # TODO: this will probably break when passing dicts
+            # or even non strawberry types
+            resolved_type = self.type_map[
+                obj.__class__._type_definition.name
+            ].implementation
+
+            assert isinstance(resolved_type, GraphQLObjectType)
+
+            return resolved_type
+
         graphql_interface = GraphQLInterfaceType(
             name=interface.name,
             fields=get_graphql_fields,
             interfaces=list(map(self.from_interface, interface.interfaces)),
             description=interface.description,
+            resolve_type=resolve_type,
         )
 
         self.type_map[interface.name] = ConcreteType(
@@ -261,14 +277,6 @@ def from_object(self, object_type: TypeDefinition) -> GraphQLObjectType:
             assert isinstance(graphql_object_type, GraphQLObjectType)  # For mypy
             return graphql_object_type
 
-        # Only define an is_type_of function for Types that implement an interface.
-        # Otherwise, leave it to the default implementation
-        is_type_of = (
-            (lambda obj, _: isinstance(obj, object_type.origin))
-            if object_type.interfaces
-            else None
-        )
-
         def get_graphql_fields() -> Dict[str, GraphQLField]:
             graphql_fields = {}
 
@@ -283,7 +291,6 @@ def get_graphql_fields() -> Dict[str, GraphQLField]:
             name=object_type.name,
             fields=get_graphql_fields,
             interfaces=list(map(self.from_interface, object_type.interfaces)),
-            is_type_of=is_type_of,
             description=object_type.description,
         )
 


Test patch:
diff --git a/tests/schema/test_interface.py b/tests/schema/test_interface.py
--- a/tests/schema/test_interface.py
+++ b/tests/schema/test_interface.py
@@ -1,5 +1,8 @@
+from dataclasses import dataclass
 from typing import List
 
+import pytest
+
 import strawberry
 
 
@@ -93,3 +96,68 @@ def always_error(self) -> Error:
         "field": "Password",
         "fix": "Choose more characters",
     }
+
+
+def test_interface_duck_typing():
+    @strawberry.interface
+    class Entity:
+        id: int
+
+    @strawberry.type
+    class Anime(Entity):
+        name: str
+
+    @dataclass
+    class AnimeORM:
+        id: int
+        name: str
+
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def anime(self) -> Anime:
+            return AnimeORM(id=1, name="One Piece")  # type: ignore
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        anime { name }
+    }"""
+
+    result = schema.execute_sync(query)
+
+    assert not result.errors
+    assert result.data == {"anime": {"name": "One Piece"}}
+
+
+@pytest.mark.xfail(reason="We don't support returning dictionaries yet")
+def test_interface_duck_typing_returning_dict():
+    @strawberry.interface
+    class Entity:
+        id: int
+
+    @strawberry.type
+    class Anime(Entity):
+        name: str
+
+    @dataclass
+    class AnimeORM:
+        id: int
+        name: str
+
+    @strawberry.type
+    class Query:
+        @strawberry.field
+        def anime(self) -> Anime:
+            return dict(id=1, name="One Piece")  # type: ignore
+
+    schema = strawberry.Schema(query=Query)
+
+    query = """{
+        anime { name }
+    }"""
+
+    result = schema.execute_sync(query)
+
+    assert not result.errors
+    assert result.data == {"anime": {"name": "One Piece"}}