Skip to content

Commit a87c83a

Browse files
committed
fix: improve handling of awaitables in default_type_resolver
Replicates graphql/graphql-js@98b6541
1 parent 2c169da commit a87c83a

2 files changed

Lines changed: 79 additions & 3 deletions

File tree

src/graphql/execution/execute.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,6 +2428,16 @@ def default_type_resolver(
24282428
append_awaitable_result(cast("Awaitable[bool]", is_type_of_result))
24292429
append_awaitable_type(type_)
24302430
elif is_type_of_result:
2431+
if awaitable_is_type_of_results:
2432+
2433+
async def await_is_type_of_and_return_type(
2434+
resolved_type_name: str = type_.name,
2435+
) -> str:
2436+
with suppress(Exception):
2437+
await gather_with_cancel(*awaitable_is_type_of_results)
2438+
return resolved_type_name
2439+
2440+
return await_is_type_of_and_return_type()
24312441
return type_.name
24322442

24332443
if awaitable_is_type_of_results:

tests/execution/test_union_interface.py

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

3-
from graphql.execution import execute_sync
3+
import pytest
4+
5+
from graphql.execution import ExecutionResult, execute, execute_sync
46
from graphql.language import parse
57
from graphql.type import (
68
GraphQLBoolean,
@@ -13,6 +15,8 @@
1315
GraphQLUnionType,
1416
)
1517

18+
pytestmark = pytest.mark.anyio
19+
1620

1721
class Dog:
1822
name: str
@@ -44,20 +48,30 @@ def __init__(self, name: str, meows: bool):
4448
self.progeny = []
4549

4650

51+
class Plant:
52+
name: str
53+
54+
def __init__(self, name: str):
55+
self.name = name
56+
57+
4758
class Person:
4859
name: str
4960
pets: list[Dog | Cat] | None
5061
friends: list[Dog | Cat | Person] | None
62+
responsibilities: list[Dog | Cat | Plant] | None
5163

5264
def __init__(
5365
self,
5466
name: str,
5567
pets: list[Dog | Cat] | None = None,
5668
friends: list[Dog | Cat | Person] | None = None,
69+
responsibilities: list[Dog | Cat | Plant] | None = None,
5770
):
5871
self.name = name
5972
self.pets = pets
6073
self.friends = friends
74+
self.responsibilities = responsibilities
6175

6276

6377
NamedType = GraphQLInterfaceType("Named", {"name": GraphQLField(GraphQLString)})
@@ -104,24 +118,39 @@ def __init__(
104118
)
105119

106120

121+
async def resolve_plant_type(_value, _info) -> bool:
122+
raise RuntimeError("Not sure if this is a plant")
123+
124+
125+
PlantType = GraphQLObjectType(
126+
"Plant",
127+
lambda: {"name": GraphQLField(GraphQLString)},
128+
interfaces=[NamedType],
129+
is_type_of=resolve_plant_type,
130+
)
131+
132+
107133
def resolve_pet_type(value, _info, _type):
108134
if isinstance(value, Dog):
109135
return DogType.name
110136
if isinstance(value, Cat):
111137
return CatType.name
112138

113139
# Not reachable. All possible types have been considered.
114-
assert False, "Unexpected pet type" # pragma: no cover
140+
pytest.fail("Unexpected pet type") # pragma: no cover
115141

116142

117143
PetType = GraphQLUnionType("Pet", [DogType, CatType], resolve_type=resolve_pet_type)
118144

145+
PetOrPlantType = GraphQLUnionType("PetOrPlantType", [PlantType, DogType, CatType])
146+
119147
PersonType = GraphQLObjectType(
120148
"Person",
121149
lambda: {
122150
"name": GraphQLField(GraphQLString),
123151
"pets": GraphQLField(GraphQLList(PetType)),
124152
"friends": GraphQLField(GraphQLList(NamedType)),
153+
"responsibilities": GraphQLField(GraphQLList(PetOrPlantType)),
125154
"progeny": GraphQLField(GraphQLList(PersonType)), # type: ignore
126155
"mother": GraphQLField(PersonType), # type: ignore
127156
"father": GraphQLField(PersonType), # type: ignore
@@ -140,8 +169,9 @@ def resolve_pet_type(value, _info, _type):
140169
odie.mother = Dog("Odie's Mom", True)
141170
odie.mother.progeny = [odie]
142171

172+
fern = Plant("Fern")
143173
liz = Person("Liz", [], [])
144-
john = Person("John", [garfield, odie], [liz, odie])
174+
john = Person("John", [garfield, odie], [liz, odie], [garfield, fern])
145175

146176

147177
def describe_execute_union_and_intersection_types():
@@ -191,6 +221,7 @@ def can_introspect_on_union_and_intersection_types():
191221
{"name": "Dog"},
192222
{"name": "Cat"},
193223
{"name": "Person"},
224+
{"name": "Plant"},
194225
],
195226
"enumValues": None,
196227
"inputFields": None,
@@ -525,3 +556,38 @@ def resolve_type(_source, info, _type):
525556
"root_value": root_value,
526557
"context": context_value,
527558
}
559+
560+
@pytest.mark.filterwarnings("error:.*was never awaited:RuntimeWarning")
561+
async def handles_rejections_from_is_type_of_after_an_is_type_of_returns_true():
562+
document = parse("""
563+
{
564+
responsibilities {
565+
__typename
566+
... on Dog {
567+
name
568+
barks
569+
}
570+
... on Cat {
571+
name
572+
meows
573+
}
574+
}
575+
}
576+
""")
577+
578+
root_value = Person("John", [], [liz], [garfield])
579+
context_value = {"authToken": "123abc"}
580+
581+
result = execute(schema, document, root_value, context_value)
582+
assert not isinstance(result, ExecutionResult)
583+
result = await result
584+
assert isinstance(result, ExecutionResult)
585+
586+
assert result == (
587+
{
588+
"responsibilities": [
589+
{"__typename": "Cat", "name": "Garfield", "meows": False},
590+
],
591+
},
592+
None,
593+
)

0 commit comments

Comments
 (0)