Repository: strawberry-graphql/strawberry

Issue:
Schema generation fails when multiple interfaces of a type implement the same base interface
<!-- Provide a general summary of the bug in the title above. -->
<!--- This template is entirely optional and can be removed, but is here to help both you and us. -->
<!--- Anything on lines wrapped in comments like these will not show up in the final text. -->

## Describe the Bug

<!-- A clear and concise description of what the bug is. -->

The following is a valid GraphQL schema, which includes interfaces that implement other interfaces:
```graphql
interface Base {
  id: String
}

interface InterfaceA implements Base {
  id: String
  fieldA: String
}

interface InterfaceB implements Base {
  id: String
  fieldB: String
}

type MyType implements InterfaceA & InterfaceB & Base {
  id: String
  fieldA: String
  fieldB: String
}
```

We implement it in Strawberry like this, using inheritance:
```python
import strawberry

@strawberry.interface
class Base:
    id: str

@strawberry.interface
class InterfaceA(Base):
    id: str
    field_a: str
    
@strawberry.interface
class InterfaceB(Base):
    id: str
    field_b: str

@strawberry.type
class MyType(InterfaceA, InterfaceB):
    id: str
    field_a: str
    field_b: str  
```

However, this results in an error when generating the GraphQL schema:
```
E           ValueError: Invalid Schema. Errors:
E           
E           ❌ Type MyType can only implement Base once.
```

As the generated schema has:
```graphql
type MyType implements InterfaceA & Base & InterfaceB & Base {
  id: String
  fieldA: String
  fieldB: String
}
```
I think the issue is in https://github.com/strawberry-graphql/strawberry/blob/20e202757ada7709e189f2b9b9cef6cb6023d5f5/strawberry/object_type.py#L33

I believe that the function `_get_interfaces` should be deduplicating the list of interfaces it gets by recursively traversing through a type's bases.

## System Information

 - Operating system: MacOS Ventura 13.1 (22C65)
 - Strawberry version (if applicable): 0.164.0

## Additional Context

<!-- Add any other relevant information about the problem here. -->



Discussion:
@mzhu22 thank you for the bug report and the suggested location where this might be going wrong! I'll get on this soon

Resolving patch:
diff --git a/strawberry/object_type.py b/strawberry/object_type.py
--- a/strawberry/object_type.py
+++ b/strawberry/object_type.py
@@ -3,6 +3,7 @@
 import sys
 import types
 from typing import (
+    Any,
     Callable,
     Dict,
     List,
@@ -30,24 +31,19 @@
 T = TypeVar("T", bound=Type)
 
 
-def _get_interfaces(cls: Type) -> List[TypeDefinition]:
-    interfaces = []
-
-    for base in cls.__bases__:
+def _get_interfaces(cls: Type[Any]) -> List[TypeDefinition]:
+    interfaces: List[TypeDefinition] = []
+    for base in cls.__mro__[1:]:  # Exclude current class
         type_definition = cast(
             Optional[TypeDefinition], getattr(base, "_type_definition", None)
         )
-
         if type_definition and type_definition.is_interface:
             interfaces.append(type_definition)
 
-        for inherited_interface in _get_interfaces(base):
-            interfaces.append(inherited_interface)
-
     return interfaces
 
 
-def _check_field_annotations(cls: Type):
+def _check_field_annotations(cls: Type[Any]):
     """Are any of the dataclass Fields missing type annotations?
 
     This is similar to the check that dataclasses do during creation, but allows us to
@@ -103,7 +99,7 @@ def _check_field_annotations(cls: Type):
             raise MissingFieldAnnotationError(field_name, cls)
 
 
-def _wrap_dataclass(cls: Type):
+def _wrap_dataclass(cls: Type[Any]):
     """Wrap a strawberry.type class with a dataclass and check for any issues
     before doing so"""
 
diff --git a/strawberry/types/types.py b/strawberry/types/types.py
--- a/strawberry/types/types.py
+++ b/strawberry/types/types.py
@@ -30,7 +30,7 @@ class TypeDefinition(StrawberryType):
     name: str
     is_input: bool
     is_interface: bool
-    origin: Type
+    origin: Type[Any]
     description: Optional[str]
     interfaces: List[TypeDefinition]
     extend: bool


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,9 +1,10 @@
 from dataclasses import dataclass
-from typing import List
+from typing import List, cast
 
 import pytest
 
 import strawberry
+from strawberry.types.types import TypeDefinition
 
 
 def test_query_interface():
@@ -198,3 +199,43 @@ def anime(self) -> Anime:
 
     assert not result.errors
     assert result.data == {"anime": {"name": "One Piece"}}
+
+
+def test_duplicated_interface_in_multi_inheritance():
+    """Test that interfaces are gathered properly via CPython's MRO.
+
+    Previously interfaces were duplicated within a "Diamond Problem" inheritance
+    scenario which is tested here. Using the MRO instead of the `__bases__` attribute of
+    a class in :py:func:`strawberry.object_type._get_interfaces` allows Python's C3
+    linearization algorithm to create a consistent precedents graph without duplicates.
+    """
+
+    @strawberry.interface
+    class Base:
+        id: str
+
+    @strawberry.interface
+    class InterfaceA(Base):
+        id: str
+        field_a: str
+
+    @strawberry.interface
+    class InterfaceB(Base):
+        id: str
+        field_b: str
+
+    @strawberry.type
+    class MyType(InterfaceA, InterfaceB):
+        id: str
+        field_a: str
+        field_b: str
+
+    @strawberry.type
+    class Query:
+        my_type: MyType
+
+    type_definition = cast(TypeDefinition, MyType._type_definition)  # type: ignore
+    origins = [i.origin for i in type_definition.interfaces]
+    assert origins == [InterfaceA, InterfaceB, Base]
+
+    strawberry.Schema(Query)  # Final sanity check to ensure schema compiles