Skip to content

Commit 05c2640

Browse files
committed
fix(incrementalDelivery): fix null bubbling with async iterables
Replicates graphql/graphql-js@0b7daed
1 parent eed117e commit 05c2640

File tree

3 files changed

+73
-15
lines changed

3 files changed

+73
-15
lines changed

src/graphql/execution/execute.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1873,29 +1873,31 @@ async def execute_stream_iterator(
18731873
label, item_path, iterator, previous_async_payload_record, self
18741874
)
18751875

1876-
awaitable_data = self.execute_stream_iterator_item(
1877-
iterator, field_modes, info, item_type, async_payload_record, item_path
1878-
)
1879-
18801876
try:
1881-
data = await awaitable_data
1877+
data = await self.execute_stream_iterator_item(
1878+
iterator,
1879+
field_modes,
1880+
info,
1881+
item_type,
1882+
async_payload_record,
1883+
item_path,
1884+
)
18821885
except StopAsyncIteration:
18831886
if async_payload_record.errors:
18841887
async_payload_record.add_items(None) # pragma: no cover
18851888
else:
18861889
del self.subsequent_payloads[async_payload_record]
18871890
break
18881891
except GraphQLError as error:
1889-
# entire stream has errored and bubbled upwards
1892+
async_payload_record.errors.append(error)
18901893
self.filter_subsequent_payloads(path, async_payload_record)
1894+
async_payload_record.add_items(None)
18911895
if iterator: # pragma: no cover else
18921896
with suppress(Exception):
18931897
await iterator.aclose() # type: ignore
18941898
# running generators cannot be closed since Python 3.8,
18951899
# so we need to remember that this iterator is already canceled
18961900
self._canceled_iterators.add(iterator)
1897-
async_payload_record.add_items(None)
1898-
async_payload_record.errors.append(error)
18991901
break
19001902

19011903
async_payload_record.add_items([data])

tests/execution/test_executor.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,11 +519,7 @@ def handles_sync_errors_combined_with_async_ones():
519519

520520
async def async_resolver(_obj, _info):
521521
nonlocal is_async_resolver_finished
522-
sleep = asyncio.sleep
523-
sleep(0)
524-
sleep(0)
525-
sleep(0)
526-
is_async_resolver_finished = True
522+
is_async_resolver_finished = True # pragma: no cover
527523

528524
schema = GraphQLSchema(
529525
GraphQLObjectType(

tests/execution/test_stream.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1020,6 +1020,68 @@ async def scalar_list(_info):
10201020

10211021
@pytest.mark.asyncio()
10221022
async def handles_async_error_in_complete_value_after_initial_count_is_reached():
1023+
document = parse(
1024+
"""
1025+
query {
1026+
friendList @stream(initialCount: 1) {
1027+
nonNullName
1028+
}
1029+
}
1030+
"""
1031+
)
1032+
1033+
async def throw():
1034+
raise RuntimeError("Oops")
1035+
1036+
async def get_friend(i):
1037+
await sleep(0)
1038+
return {"nonNullName": throw() if i < 0 else friends[i].name}
1039+
1040+
def get_friends(_info):
1041+
return [get_friend(0), get_friend(-1), get_friend(1)]
1042+
1043+
result = await complete(
1044+
document,
1045+
{
1046+
"friendList": get_friends,
1047+
},
1048+
)
1049+
assert result == [
1050+
{
1051+
"data": {
1052+
"friendList": [{"nonNullName": "Luke"}],
1053+
},
1054+
"hasNext": True,
1055+
},
1056+
{
1057+
"incremental": [
1058+
{
1059+
"items": [None],
1060+
"path": ["friendList", 1],
1061+
"errors": [
1062+
{
1063+
"message": "Oops",
1064+
"locations": [{"line": 4, "column": 17}],
1065+
"path": ["friendList", 1, "nonNullName"],
1066+
},
1067+
],
1068+
},
1069+
],
1070+
"hasNext": True,
1071+
},
1072+
{
1073+
"incremental": [
1074+
{
1075+
"items": [{"nonNullName": "Han"}],
1076+
"path": ["friendList", 2],
1077+
},
1078+
],
1079+
"hasNext": False,
1080+
},
1081+
]
1082+
1083+
@pytest.mark.asyncio()
1084+
async def handles_async_error_in_complete_value_for_non_nullable_list():
10231085
document = parse(
10241086
"""
10251087
query {
@@ -1230,7 +1292,6 @@ async def friend_list(_info):
12301292
}
12311293

12321294
@pytest.mark.asyncio()
1233-
@pytest.mark.filterwarnings("ignore:.* was never awaited:RuntimeWarning")
12341295
async def does_not_filter_payloads_when_null_error_is_in_a_different_path():
12351296
document = parse(
12361297
"""
@@ -1298,7 +1359,6 @@ async def friend_list(_info):
12981359
]
12991360

13001361
@pytest.mark.asyncio()
1301-
@pytest.mark.filterwarnings("ignore:.* was never awaited:RuntimeWarning")
13021362
async def filters_stream_payloads_that_are_nulled_in_a_deferred_payload():
13031363
document = parse(
13041364
"""

0 commit comments

Comments
 (0)