From 5cfac3ce6dd82dd2b435d3df543e84670ed0323d Mon Sep 17 00:00:00 2001 From: Iota Date: Sun, 26 May 2024 13:30:04 -0600 Subject: [PATCH 01/15] Merge unions --- strawberry/schema/schema_converter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index de8e2f61d0..91c795eaff 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -856,9 +856,13 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: if isinstance(graphql_type, GraphQLInputObjectType): raise InvalidTypeInputForUnion(graphql_type) - assert isinstance(graphql_type, GraphQLObjectType) + assert isinstance(graphql_type, GraphQLObjectType | GraphQLUnionType) - graphql_types.append(graphql_type) + if isinstance(graphql_type, GraphQLUnionType): + # merge child types + graphql_types += list(graphql_type.types) + else: + graphql_types.append(graphql_type) graphql_union = GraphQLUnionType( name=union_name, From 747b2ea3b400f06a6e613f29fdc0ef9ec46043dd Mon Sep 17 00:00:00 2001 From: Jacob Allen Date: Sun, 26 May 2024 13:58:09 -0600 Subject: [PATCH 02/15] Use extend instead of += Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- strawberry/schema/schema_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index 91c795eaff..ac30881c09 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -860,7 +860,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: if isinstance(graphql_type, GraphQLUnionType): # merge child types - graphql_types += list(graphql_type.types) + graphql_types.extend(graphql_type.types) else: graphql_types.append(graphql_type) From 674b0bbd88b435759ff6cfbfa907d8572459038f Mon Sep 17 00:00:00 2001 From: Jacob Allen Date: Sun, 26 May 2024 13:58:39 -0600 Subject: [PATCH 03/15] Sourcery suggestion Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- strawberry/schema/schema_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index ac30881c09..5549ea754e 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -859,7 +859,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: assert isinstance(graphql_type, GraphQLObjectType | GraphQLUnionType) if isinstance(graphql_type, GraphQLUnionType): - # merge child types + # Add the child types of the GraphQLUnionType to the list of graphql_types graphql_types.extend(graphql_type.types) else: graphql_types.append(graphql_type) From b2c7fc10b8f8fade163119dc73cb7255b3d0de3e Mon Sep 17 00:00:00 2001 From: Jacob Allen Date: Sun, 26 May 2024 13:58:46 -0600 Subject: [PATCH 04/15] Sourcery suggestion Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- strawberry/schema/schema_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index 5549ea754e..1069f556fa 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -858,6 +858,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: raise InvalidTypeInputForUnion(graphql_type) assert isinstance(graphql_type, GraphQLObjectType | GraphQLUnionType) + # If the graphql_type is a GraphQLUnionType, merge its child types if isinstance(graphql_type, GraphQLUnionType): # Add the child types of the GraphQLUnionType to the list of graphql_types graphql_types.extend(graphql_type.types) From 0f31c051e192196bf15ddbb023b73e8381508212 Mon Sep 17 00:00:00 2001 From: Iota Date: Sun, 26 May 2024 14:02:32 -0600 Subject: [PATCH 05/15] Add RELEASE.md --- RELEASE.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 0000000000..732f56afa3 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,3 @@ +Release type: patch + +Attempt to merge union types during schema conversion. From e63fed0e29a166714bb336c039352cbc12743ca1 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 21:40:47 +0100 Subject: [PATCH 06/15] Fix check on older python versions --- strawberry/schema/schema_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index 1069f556fa..eb8fd76549 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -856,7 +856,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: if isinstance(graphql_type, GraphQLInputObjectType): raise InvalidTypeInputForUnion(graphql_type) - assert isinstance(graphql_type, GraphQLObjectType | GraphQLUnionType) + assert isinstance(graphql_type, (GraphQLObjectType, GraphQLUnionType)) # If the graphql_type is a GraphQLUnionType, merge its child types if isinstance(graphql_type, GraphQLUnionType): From 318b47b8dd9680b635dc6a5b7e0bf8c5608b27d2 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 21:57:48 +0100 Subject: [PATCH 07/15] Add test that was already passing --- tests/schema/test_union.py | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index fe8e2ec98f..d3520f76f6 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -856,3 +856,59 @@ class Query: assert not result.errors assert result.data["something"] == {"__typename": "A", "a": 5} + + +def test_generic_union_with_annotated(): + @strawberry.type + class SomeType: + id: strawberry.ID + name: str + + @strawberry.type + class NotFoundError: + id: strawberry.ID + message: str + + T = TypeVar("T") + + @strawberry.type + class ObjectQueries(Generic[T]): + @strawberry.field + def by_id( + self, id: strawberry.ID + ) -> Annotated[Union[T, NotFoundError], strawberry.union("ByIdResult")]: ... + + @strawberry.type + class Query: + @strawberry.field + def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: + raise NotImplementedError() + + schema = strawberry.Schema(Query) + + assert ( + str(schema) + == textwrap.dedent( + """ + type NotFoundError { + id: ID! + message: String! + } + + type Query { + someTypeQueries(id: ID!): SomeTypeObjectQueries! + } + + type SomeType { + id: ID! + name: String! + } + + union SomeTypeNotFoundError = SomeType | NotFoundError + + type SomeTypeObjectQueries { + byId(id: ID!): SomeTypeNotFoundError! + } + """ + ).strip() + ) From 42a582783e534aa4bb98d48e2f8c80a26b1f34ba Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 22:20:53 +0100 Subject: [PATCH 08/15] Add missing test --- tests/schema/test_union.py | 62 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index d3520f76f6..1b81888448 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -858,6 +858,7 @@ class Query: assert result.data["something"] == {"__typename": "A", "a": 5} +@pytest.mark.xfail(reason="Not supported yet") def test_generic_union_with_annotated(): @strawberry.type class SomeType: @@ -886,6 +887,7 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: schema = strawberry.Schema(Query) + # TODO: check the name assert ( str(schema) == textwrap.dedent( @@ -896,7 +898,7 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: } type Query { - someTypeQueries(id: ID!): SomeTypeObjectQueries! + someTypeQueries(id: ID!): SomeTypeByIdResult! } type SomeType { @@ -907,7 +909,63 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: union SomeTypeNotFoundError = SomeType | NotFoundError type SomeTypeObjectQueries { - byId(id: ID!): SomeTypeNotFoundError! + byId(id: ID!): SomeTypeByIdResult! + } + """ + ).strip() + ) + + +def test_generic_union_with_annotated_inside(): + @strawberry.type + class SomeType: + id: strawberry.ID + name: str + + @strawberry.type + class NotFoundError: + id: strawberry.ID + message: str + + T = TypeVar("T") + + @strawberry.type + class ObjectQueries(Generic[T]): + @strawberry.field + def by_id( + self, id: strawberry.ID + ) -> Union[T, Annotated[NotFoundError, strawberry.union("ByIdResult")]]: ... + + @strawberry.type + class Query: + @strawberry.field + def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: + return ObjectQueries(SomeType) + + schema = strawberry.Schema(Query) + + assert ( + str(schema) + == textwrap.dedent( + """ + type NotFoundError { + id: ID! + message: String! + } + + type Query { + someTypeQueries(id: ID!): SomeTypeObjectQueries! + } + + type SomeType { + id: ID! + name: String! + } + + union SomeTypeByIdResult = SomeType | NotFoundError + + type SomeTypeObjectQueries { + byId(id: ID!): SomeTypeByIdResult! } """ ).strip() From ac29523ae97649e6d787f7544abee6e048c7da87 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 23:21:18 +0100 Subject: [PATCH 09/15] Add support for generic unions --- strawberry/schema/name_converter.py | 19 ++++++++++++++----- strawberry/schema/schema_converter.py | 1 + strawberry/union.py | 7 ++++++- tests/schema/test_union.py | 6 ++---- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/strawberry/schema/name_converter.py b/strawberry/schema/name_converter.py index ae6b2ba47d..f61d22903f 100644 --- a/strawberry/schema/name_converter.py +++ b/strawberry/schema/name_converter.py @@ -107,8 +107,14 @@ def from_union(self, union: StrawberryUnion) -> str: return union.graphql_name name = "" + types = union.types - for type_ in union.types: + if union.concrete_of and union.concrete_of.graphql_name: + concrete_of_types = set(union.concrete_of.types) + + types = [type_ for type_ in types if type_ not in concrete_of_types] + + for type_ in types: if isinstance(type_, LazyType): type_ = cast("StrawberryType", type_.resolve_type()) # noqa: PLW2901 @@ -121,6 +127,9 @@ def from_union(self, union: StrawberryUnion) -> str: name += type_name + if union.concrete_of and union.concrete_of.graphql_name: + name += union.concrete_of.graphql_name + return name def from_generic( @@ -133,12 +142,12 @@ def from_generic( names: List[str] = [] for type_ in types: - name = self.get_from_type(type_) + name = self.get_name_from_type(type_) names.append(name) return "".join(names) + generic_type_name - def get_from_type(self, type_: Union[StrawberryType, type]) -> str: + def get_name_from_type(self, type_: Union[StrawberryType, type]) -> str: type_ = eval_type(type_) if isinstance(type_, LazyType): @@ -148,9 +157,9 @@ def get_from_type(self, type_: Union[StrawberryType, type]) -> str: elif isinstance(type_, StrawberryUnion): name = type_.graphql_name if type_.graphql_name else self.from_union(type_) elif isinstance(type_, StrawberryList): - name = self.get_from_type(type_.of_type) + "List" + name = self.get_name_from_type(type_.of_type) + "List" elif isinstance(type_, StrawberryOptional): - name = self.get_from_type(type_.of_type) + "Optional" + name = self.get_name_from_type(type_.of_type) + "Optional" elif hasattr(type_, "_scalar_definition"): strawberry_type = type_._scalar_definition diff --git a/strawberry/schema/schema_converter.py b/strawberry/schema/schema_converter.py index eb8fd76549..9911afcb70 100644 --- a/strawberry/schema/schema_converter.py +++ b/strawberry/schema/schema_converter.py @@ -851,6 +851,7 @@ def from_union(self, union: StrawberryUnion) -> GraphQLUnionType: return graphql_union graphql_types: List[GraphQLObjectType] = [] + for type_ in union.types: graphql_type = self.from_type(type_) diff --git a/strawberry/union.py b/strawberry/union.py index f0a319a5a9..02b7e85fe7 100644 --- a/strawberry/union.py +++ b/strawberry/union.py @@ -67,6 +67,7 @@ def __init__( self.directives = directives self._source_file = None self._source_line = None + self.concrete_of: Optional[StrawberryUnion] = None def __eq__(self, other: object) -> bool: if isinstance(other, StrawberryType): @@ -139,6 +140,7 @@ def copy_with( return self new_types = [] + for type_ in self.types: new_type: Union[StrawberryType, type] @@ -154,10 +156,13 @@ def copy_with( new_types.append(new_type) - return StrawberryUnion( + new_union = StrawberryUnion( type_annotations=tuple(map(StrawberryAnnotation, new_types)), description=self.description, ) + new_union.concrete_of = self + + return new_union def __call__(self, *args: str, **kwargs: Any) -> NoReturn: """Do not use. diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index 1b81888448..3c8ecae1f1 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -858,7 +858,6 @@ class Query: assert result.data["something"] == {"__typename": "A", "a": 5} -@pytest.mark.xfail(reason="Not supported yet") def test_generic_union_with_annotated(): @strawberry.type class SomeType: @@ -887,7 +886,6 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: schema = strawberry.Schema(Query) - # TODO: check the name assert ( str(schema) == textwrap.dedent( @@ -898,7 +896,7 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: } type Query { - someTypeQueries(id: ID!): SomeTypeByIdResult! + someTypeQueries(id: ID!): SomeTypeObjectQueries! } type SomeType { @@ -906,7 +904,7 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: name: String! } - union SomeTypeNotFoundError = SomeType | NotFoundError + union SomeTypeByIdResult = SomeType | NotFoundError type SomeTypeObjectQueries { byId(id: ID!): SomeTypeByIdResult! From 576593b14082c157e00c594fddabcef499dd1b47 Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 23:32:12 +0100 Subject: [PATCH 10/15] Lint --- strawberry/schema/name_converter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/strawberry/schema/name_converter.py b/strawberry/schema/name_converter.py index f61d22903f..cd56c25e07 100644 --- a/strawberry/schema/name_converter.py +++ b/strawberry/schema/name_converter.py @@ -107,7 +107,7 @@ def from_union(self, union: StrawberryUnion) -> str: return union.graphql_name name = "" - types = union.types + types: List[Union[StrawberryType, type]] = union.types if union.concrete_of and union.concrete_of.graphql_name: concrete_of_types = set(union.concrete_of.types) From 280d8419940bbb5aed1c85d881149cb97b3a020e Mon Sep 17 00:00:00 2001 From: Patrick Arminio Date: Sun, 26 May 2024 23:33:55 +0100 Subject: [PATCH 11/15] Lint --- strawberry/schema/name_converter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/strawberry/schema/name_converter.py b/strawberry/schema/name_converter.py index cd56c25e07..8c12ef214f 100644 --- a/strawberry/schema/name_converter.py +++ b/strawberry/schema/name_converter.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional, Union, cast +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, cast from typing_extensions import Protocol from strawberry.custom_scalar import ScalarDefinition @@ -107,12 +107,12 @@ def from_union(self, union: StrawberryUnion) -> str: return union.graphql_name name = "" - types: List[Union[StrawberryType, type]] = union.types + types: Tuple[StrawberryType, ...] = union.types if union.concrete_of and union.concrete_of.graphql_name: concrete_of_types = set(union.concrete_of.types) - types = [type_ for type_ in types if type_ not in concrete_of_types] + types = tuple(type_ for type_ in types if type_ not in concrete_of_types) for type_ in types: if isinstance(type_, LazyType): From 99cc7453976990880d760c34ae59e39e9bbaa553 Mon Sep 17 00:00:00 2001 From: Iota Date: Thu, 30 May 2024 21:00:26 -0600 Subject: [PATCH 12/15] Add test for annotated union with two generics --- tests/schema/test_union.py | 63 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index 3c8ecae1f1..1aa13d5990 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -968,3 +968,66 @@ def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: """ ).strip() ) + + +def test_annoted_union_with_two_generics(): + @strawberry.type + class SomeType: + a: str + + @strawberry.type + class OtherType: + b: str + + @strawberry.type + class NotFoundError: + message: str + + T = TypeVar("T") + U = TypeVar("U") + + @strawberry.type + class UnionObjectQueries(Generic[T, U]): + @strawberry.field + def by_id( + self, id: strawberry.ID + ) -> T | Annotated[U | NotFoundError, strawberry.union("ByIdResult")]: ... + + @strawberry.type + class Query: + @strawberry.field + def some_type_queries( + self, id: strawberry.ID + ) -> UnionObjectQueries[SomeType, OtherType]: + return UnionObjectQueries() + + schema = strawberry.Schema(Query) + + assert ( + str(schema) + == textwrap.dedent( + """ + type NotFoundError { + message: String! + } + + type OtherType { + b: String! + } + + type Query { + someTypeQueries(id: ID!): SomeTypeOtherTypeUnionObjectQueries! + } + + type SomeType { + a: String! + } + + union SomeTypeOtherTypeByIdResult = SomeType | OtherType | NotFoundError + + type SomeTypeOtherTypeUnionObjectQueries { + byId(id: ID!): SomeTypeOtherTypeByIdResult! + } + """ + ).strip() + ) From d6c58ed65fb3b0ae6dce1f42bf9d5d72716310f9 Mon Sep 17 00:00:00 2001 From: Iota Date: Thu, 30 May 2024 21:08:41 -0600 Subject: [PATCH 13/15] Rewrite test to support python <= 3.9 --- tests/schema/test_union.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index 1aa13d5990..67c26949e9 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -991,7 +991,9 @@ class UnionObjectQueries(Generic[T, U]): @strawberry.field def by_id( self, id: strawberry.ID - ) -> T | Annotated[U | NotFoundError, strawberry.union("ByIdResult")]: ... + ) -> Union[ + T, Annotated[Union[U, NotFoundError], strawberry.union("ByIdResult")] + ]: ... @strawberry.type class Query: From b34106ee04380c649832cd7a58a036e3f06a9c02 Mon Sep 17 00:00:00 2001 From: Jacob Allen Date: Thu, 30 May 2024 22:10:17 -0600 Subject: [PATCH 14/15] Remove unused function bodies from tests --- tests/schema/test_union.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/schema/test_union.py b/tests/schema/test_union.py index 67c26949e9..97e07744b5 100644 --- a/tests/schema/test_union.py +++ b/tests/schema/test_union.py @@ -937,8 +937,7 @@ def by_id( @strawberry.type class Query: @strawberry.field - def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: - return ObjectQueries(SomeType) + def some_type_queries(self, id: strawberry.ID) -> ObjectQueries[SomeType]: ... schema = strawberry.Schema(Query) @@ -1000,8 +999,7 @@ class Query: @strawberry.field def some_type_queries( self, id: strawberry.ID - ) -> UnionObjectQueries[SomeType, OtherType]: - return UnionObjectQueries() + ) -> UnionObjectQueries[SomeType, OtherType]: ... schema = strawberry.Schema(Query) From 65e85f1810a68c47980528333fc838012e8c1054 Mon Sep 17 00:00:00 2001 From: Jacob Allen Date: Sat, 19 Oct 2024 23:40:54 -0600 Subject: [PATCH 15/15] Update RELEASE.md Co-authored-by: Thiago Bellini Ribeiro --- RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE.md b/RELEASE.md index 732f56afa3..e41e9220d4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,3 +1,3 @@ -Release type: patch +Release type: minor Attempt to merge union types during schema conversion.