Skip to content

Commit 63216e3

Browse files
committed
Add filtering for async iterator lists
Replicates graphql/graphql-js@5ae2e06
1 parent d42eddb commit 63216e3

File tree

2 files changed

+70
-15
lines changed

2 files changed

+70
-15
lines changed

src/graphql/execution/execute.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,7 @@ async def catch_error(
12331233
error = located_error(
12341234
raw_error, field_nodes, field_path.as_list()
12351235
)
1236+
self.filter_subsequent_payloads(field_path)
12361237
handle_field_error(error, item_type, errors)
12371238
return None
12381239

@@ -1243,10 +1244,12 @@ async def catch_error(
12431244
except Exception as raw_error:
12441245
append_result(None)
12451246
error = located_error(raw_error, field_nodes, field_path.as_list())
1247+
self.filter_subsequent_payloads(field_path)
12461248
handle_field_error(error, item_type, errors)
12471249
except Exception as raw_error:
12481250
append_result(None)
12491251
error = located_error(raw_error, field_nodes, field_path.as_list())
1252+
self.filter_subsequent_payloads(field_path)
12501253
handle_field_error(error, item_type, errors)
12511254
break
12521255
index += 1

tests/execution/test_defer.py

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from asyncio import sleep
2-
from typing import Any, Dict, List, NamedTuple
2+
from typing import Any, AsyncGenerator, Dict, List, NamedTuple
33

44
import pytest
55
from graphql.error import GraphQLError
@@ -15,7 +15,7 @@
1515
)
1616
from graphql.execution.execute import DeferredFragmentRecord
1717
from graphql.language import DocumentNode, parse
18-
from graphql.pyutils import Path
18+
from graphql.pyutils import Path, is_awaitable
1919
from graphql.type import (
2020
GraphQLField,
2121
GraphQLID,
@@ -26,17 +26,35 @@
2626
GraphQLString,
2727
)
2828

29+
30+
def resolve_null_sync(_obj, _info) -> None:
31+
"""A resolver returning a null value synchronously."""
32+
return
33+
34+
35+
async def resolve_null_async(_obj, _info) -> None:
36+
"""A resolver returning a null value asynchronously."""
37+
return
38+
39+
2940
friend_type = GraphQLObjectType(
30-
"Friend", {"id": GraphQLField(GraphQLID), "name": GraphQLField(GraphQLString)}
41+
"Friend",
42+
{
43+
"id": GraphQLField(GraphQLID),
44+
"name": GraphQLField(GraphQLString),
45+
"asyncNonNullErrorField": GraphQLField(
46+
GraphQLNonNull(GraphQLString), resolve=resolve_null_async
47+
),
48+
},
3149
)
3250

3351

3452
class Friend(NamedTuple):
35-
name: str
3653
id: int
54+
name: str
3755

3856

39-
friends = [Friend("Han", 2), Friend("Leia", 3), Friend("C-3PO", 4)]
57+
friends = [Friend(2, "Han"), Friend(3, "Leia"), Friend(4, "C-3PO")]
4058

4159

4260
async def resolve_slow(_obj, _info) -> str:
@@ -50,14 +68,10 @@ async def resolve_bad(_obj, _info) -> str:
5068
raise RuntimeError("bad")
5169

5270

53-
def resolve_null_sync(_obj, _info) -> None:
54-
"""Simulate a resolver returning a null value synchronously."""
55-
return
56-
57-
58-
async def resolve_null_async(_obj, _info) -> None:
59-
"""Simulate a resolver returning a null value asynchronously."""
60-
return
71+
async def resolve_friends_async(_obj, _info) -> AsyncGenerator[Friend, None]:
72+
"""A slow async generator yielding the first friend."""
73+
await sleep(0)
74+
yield friends[0]
6175

6276

6377
hero_type = GraphQLObjectType(
@@ -76,10 +90,13 @@ async def resolve_null_async(_obj, _info) -> None:
7690
"friends": GraphQLField(
7791
GraphQLList(friend_type), resolve=lambda _obj, _info: friends
7892
),
93+
"asyncFriends": GraphQLField(
94+
GraphQLList(friend_type), resolve=resolve_friends_async
95+
),
7996
},
8097
)
8198

82-
hero = Friend("Luke", 1)
99+
hero = Friend(1, "Luke")
83100

84101
query = GraphQLObjectType(
85102
"Query", {"hero": GraphQLField(hero_type, resolve=lambda _obj, _info: hero)}
@@ -90,6 +107,8 @@ async def resolve_null_async(_obj, _info) -> None:
90107

91108
async def complete(document: DocumentNode, root_value: Any = None) -> Any:
92109
result = experimental_execute_incrementally(schema, document, root_value)
110+
if is_awaitable(result):
111+
result = await result
93112

94113
if isinstance(result, ExperimentalIncrementalExecutionResults):
95114
results: List[Any] = [result.initial_result.formatted]
@@ -882,6 +901,38 @@ async def returns_payloads_from_synchronous_data_in_correct_order():
882901
},
883902
]
884903

904+
@pytest.mark.asyncio()
905+
async def filters_deferred_payloads_when_list_item_from_async_iterable_nulled():
906+
document = parse(
907+
"""
908+
query {
909+
hero {
910+
asyncFriends {
911+
asyncNonNullErrorField
912+
...NameFragment @defer
913+
}
914+
}
915+
}
916+
fragment NameFragment on Friend {
917+
name
918+
}
919+
"""
920+
)
921+
922+
result = await complete(document)
923+
924+
assert result == {
925+
"data": {"hero": {"asyncFriends": [None]}},
926+
"errors": [
927+
{
928+
"message": "Cannot return null for non-nullable field"
929+
" Friend.asyncNonNullErrorField.",
930+
"locations": [{"line": 5, "column": 19}],
931+
"path": ["hero", "asyncFriends", 0, "asyncNonNullErrorField"],
932+
}
933+
],
934+
}
935+
885936
@pytest.mark.asyncio()
886937
async def original_execute_function_throws_error_if_deferred_and_all_is_sync():
887938
document = parse(
@@ -918,7 +969,8 @@ async def original_execute_function_throws_error_if_deferred_and_not_all_is_sync
918969
[
919970
{
920971
"message": "Executing this GraphQL operation would unexpectedly"
921-
" produce multiple payloads (due to @defer or @stream directive)"
972+
" produce multiple payloads"
973+
" (due to @defer or @stream directive)"
922974
}
923975
],
924976
)

0 commit comments

Comments
 (0)