Skip to content

Commit e0d35cc

Browse files
authored
fix: fix DuplicatedTypeName exception raised for nested generics (#3946)
1 parent 79214e6 commit e0d35cc

File tree

3 files changed

+123
-36
lines changed

3 files changed

+123
-36
lines changed

RELEASE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Release type: patch
2+
3+
This release fixes an issue where `DuplicatedTypeName` exception would be raised
4+
for nested generics like in the example below:
5+
6+
```python
7+
from typing import Generic, TypeVar
8+
9+
import strawberry
10+
11+
T = TypeVar("T")
12+
13+
14+
@strawberry.type
15+
class Wrapper(Generic[T]):
16+
value: T
17+
18+
19+
@strawberry.type
20+
class Query:
21+
a: Wrapper[Wrapper[int]]
22+
b: Wrapper[Wrapper[int]]
23+
24+
25+
schema = strawberry.Schema(query=Query)
26+
```
27+
28+
This piece of code and similar ones will now work correctly.

strawberry/schema/schema_converter.py

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1003,43 +1003,11 @@ def validate_same_type_definition(
10031003
first_type_definition = cached_type.definition
10041004
second_type_definition = type_definition
10051005

1006-
# TODO: maybe move this on the StrawberryType class
1007-
if (
1008-
isinstance(first_type_definition, StrawberryObjectDefinition)
1009-
and isinstance(second_type_definition, StrawberryObjectDefinition)
1010-
and first_type_definition.concrete_of is not None
1011-
and first_type_definition.concrete_of == second_type_definition.concrete_of
1012-
and (
1013-
first_type_definition.type_var_map.keys()
1014-
== second_type_definition.type_var_map.keys()
1015-
)
1006+
if self.is_same_type_definition(
1007+
first_type_definition,
1008+
second_type_definition,
10161009
):
1017-
# manually compare type_var_maps while resolving any lazy types
1018-
# so that they're considered equal to the actual types they're referencing
1019-
equal = True
1020-
for type_var, type1 in first_type_definition.type_var_map.items():
1021-
type2 = second_type_definition.type_var_map[type_var]
1022-
# both lazy types are always resolved because two different lazy types
1023-
# may be referencing the same actual type
1024-
if isinstance(type1, LazyType):
1025-
type1 = type1.resolve_type() # noqa: PLW2901
1026-
elif isinstance(type1, StrawberryOptional) and isinstance(
1027-
type1.of_type, LazyType
1028-
):
1029-
type1.of_type = type1.of_type.resolve_type()
1030-
1031-
if isinstance(type2, LazyType):
1032-
type2 = type2.resolve_type()
1033-
elif isinstance(type2, StrawberryOptional) and isinstance(
1034-
type2.of_type, LazyType
1035-
):
1036-
type2.of_type = type2.of_type.resolve_type()
1037-
1038-
if type1 != type2:
1039-
equal = False
1040-
break
1041-
if equal:
1042-
return
1010+
return
10431011

10441012
if isinstance(second_type_definition, StrawberryObjectDefinition):
10451013
first_origin = second_type_definition.origin
@@ -1057,5 +1025,63 @@ def validate_same_type_definition(
10571025

10581026
raise DuplicatedTypeName(first_origin, second_origin, name)
10591027

1028+
def is_same_type_definition(
1029+
self,
1030+
first_type_definition: StrawberryObjectDefinition | StrawberryType,
1031+
second_type_definition: StrawberryObjectDefinition | StrawberryType,
1032+
) -> bool:
1033+
# TODO: maybe move this on the StrawberryType class
1034+
if (
1035+
not isinstance(first_type_definition, StrawberryObjectDefinition)
1036+
or not isinstance(second_type_definition, StrawberryObjectDefinition)
1037+
or first_type_definition.concrete_of is None
1038+
or first_type_definition.concrete_of != second_type_definition.concrete_of
1039+
or (
1040+
first_type_definition.type_var_map.keys()
1041+
!= second_type_definition.type_var_map.keys()
1042+
)
1043+
):
1044+
return False
1045+
1046+
# manually compare type_var_maps while resolving any lazy types
1047+
# so that they're considered equal to the actual types they're referencing
1048+
for type_var, type1 in first_type_definition.type_var_map.items():
1049+
type2 = second_type_definition.type_var_map[type_var]
1050+
1051+
# both lazy types are always resolved because two different lazy types
1052+
# may be referencing the same actual type
1053+
if isinstance(type1, LazyType):
1054+
type1 = type1.resolve_type() # noqa: PLW2901
1055+
elif isinstance(type1, StrawberryOptional) and isinstance(
1056+
type1.of_type, LazyType
1057+
):
1058+
type1.of_type = type1.of_type.resolve_type()
1059+
1060+
if isinstance(type2, LazyType):
1061+
type2 = type2.resolve_type()
1062+
elif isinstance(type2, StrawberryOptional) and isinstance(
1063+
type2.of_type, LazyType
1064+
):
1065+
type2.of_type = type2.of_type.resolve_type()
1066+
1067+
same_type = type1 == type2
1068+
# If both types have object definitions, we are handling a nested generic
1069+
# type like `Foo[Foo[int]]`, meaning we need to compare their type definitions
1070+
# as they will actually be different instances of the type
1071+
if (
1072+
not same_type
1073+
and has_object_definition(type1)
1074+
and has_object_definition(type2)
1075+
):
1076+
same_type = self.is_same_type_definition(
1077+
type1.__strawberry_definition__,
1078+
type2.__strawberry_definition__,
1079+
)
1080+
1081+
if not same_type:
1082+
return False
1083+
1084+
return True
1085+
10601086

10611087
__all__ = ["GraphQLCoreConverter"]

tests/schema/test_generics_nested.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,36 @@ def users(self) -> Union[Connection[User], Fallback]:
363363
"edge": {"__typename": "UserEdge", "node": {"name": "Patrick"}},
364364
}
365365
}
366+
367+
368+
def test_does_not_raise_duplicated_type_error():
369+
T = TypeVar("T")
370+
371+
@strawberry.type
372+
class Wrapper(Generic[T]):
373+
value: T
374+
375+
@strawberry.type
376+
class Query:
377+
a: Wrapper[Wrapper[int]]
378+
b: Wrapper[Wrapper[int]]
379+
380+
schema = strawberry.Schema(query=Query)
381+
382+
expected_schema = textwrap.dedent(
383+
"""
384+
type IntWrapper {
385+
value: Int!
386+
}
387+
388+
type IntWrapperWrapper {
389+
value: IntWrapper!
390+
}
391+
392+
type Query {
393+
a: IntWrapperWrapper!
394+
b: IntWrapperWrapper!
395+
}
396+
"""
397+
).strip()
398+
assert str(schema) == expected_schema

0 commit comments

Comments
 (0)