Skip to content

Commit eb9edd5

Browse files
committed
incremental: subsequent result records should not store parent references
Replicates graphql/graphql-js@fae5da5
1 parent 448d045 commit eb9edd5

File tree

5 files changed

+122
-133
lines changed

5 files changed

+122
-133
lines changed

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,17 @@
161161
GraphQLTypeResolver
162162
GroupedFieldSet
163163
IncrementalDataRecord
164+
InitialResultRecord
164165
Middleware
166+
SubsequentDataRecord
165167
asyncio.events.AbstractEventLoop
166168
graphql.execution.collect_fields.FieldsAndPatches
167169
graphql.execution.map_async_iterable.map_async_iterable
168170
graphql.execution.Middleware
169171
graphql.execution.execute.ExperimentalIncrementalExecutionResults
170172
graphql.execution.execute.StreamArguments
171173
graphql.execution.incremental_publisher.IncrementalPublisher
174+
graphql.execution.incremental_publisher.InitialResultRecord
172175
graphql.execution.incremental_publisher.StreamItemsRecord
173176
graphql.execution.incremental_publisher.DeferredFragmentRecord
174177
graphql.language.lexer.EscapeSequence

src/graphql/execution/execute.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@
8686
IncrementalDataRecord,
8787
IncrementalPublisher,
8888
IncrementalResult,
89+
InitialResultRecord,
8990
StreamItemsRecord,
91+
SubsequentDataRecord,
9092
SubsequentIncrementalExecutionResult,
9193
)
9294
from .middleware import MiddlewareManager
@@ -352,7 +354,6 @@ class ExecutionContext:
352354
field_resolver: GraphQLFieldResolver
353355
type_resolver: GraphQLTypeResolver
354356
subscribe_field_resolver: GraphQLFieldResolver
355-
errors: list[GraphQLError]
356357
incremental_publisher: IncrementalPublisher
357358
middleware_manager: MiddlewareManager | None
358359

@@ -371,7 +372,6 @@ def __init__(
371372
field_resolver: GraphQLFieldResolver,
372373
type_resolver: GraphQLTypeResolver,
373374
subscribe_field_resolver: GraphQLFieldResolver,
374-
errors: list[GraphQLError],
375375
incremental_publisher: IncrementalPublisher,
376376
middleware_manager: MiddlewareManager | None,
377377
is_awaitable: Callable[[Any], bool] | None,
@@ -385,7 +385,6 @@ def __init__(
385385
self.field_resolver = field_resolver
386386
self.type_resolver = type_resolver
387387
self.subscribe_field_resolver = subscribe_field_resolver
388-
self.errors = errors
389388
self.incremental_publisher = incremental_publisher
390389
self.middleware_manager = middleware_manager
391390
if is_awaitable:
@@ -478,7 +477,6 @@ def build(
478477
field_resolver or default_field_resolver,
479478
type_resolver or default_type_resolver,
480479
subscribe_field_resolver or default_field_resolver,
481-
[],
482480
IncrementalPublisher(),
483481
middleware_manager,
484482
is_awaitable,
@@ -514,15 +512,14 @@ def build_per_event_execution_context(self, payload: Any) -> ExecutionContext:
514512
self.field_resolver,
515513
self.type_resolver,
516514
self.subscribe_field_resolver,
517-
[],
518-
# no need to update incrementalPublisher,
519-
# incremental delivery is not supported for subscriptions
520515
self.incremental_publisher,
521516
self.middleware_manager,
522517
self.is_awaitable,
523518
)
524519

525-
def execute_operation(self) -> AwaitableOrValue[dict[str, Any]]:
520+
def execute_operation(
521+
self, initial_result_record: InitialResultRecord
522+
) -> AwaitableOrValue[dict[str, Any]]:
526523
"""Execute an operation.
527524
528525
Implements the "Executing operations" section of the spec.
@@ -551,12 +548,17 @@ def execute_operation(self) -> AwaitableOrValue[dict[str, Any]]:
551548
self.execute_fields_serially
552549
if operation.operation == OperationType.MUTATION
553550
else self.execute_fields
554-
)(root_type, root_value, None, grouped_field_set) # type: ignore
551+
)(root_type, root_value, None, grouped_field_set, initial_result_record)
555552

556553
for patch in patches:
557554
label, patch_grouped_filed_set = patch
558555
self.execute_deferred_fragment(
559-
root_type, root_value, patch_grouped_filed_set, label, None
556+
root_type,
557+
root_value,
558+
patch_grouped_filed_set,
559+
initial_result_record,
560+
label,
561+
None,
560562
)
561563

562564
return result
@@ -567,6 +569,7 @@ def execute_fields_serially(
567569
source_value: Any,
568570
path: Path | None,
569571
grouped_field_set: GroupedFieldSet,
572+
incremental_data_record: IncrementalDataRecord,
570573
) -> AwaitableOrValue[dict[str, Any]]:
571574
"""Execute the given fields serially.
572575
@@ -581,7 +584,11 @@ def reducer(
581584
response_name, field_group = field_item
582585
field_path = Path(path, response_name, parent_type.name)
583586
result = self.execute_field(
584-
parent_type, source_value, field_group, field_path
587+
parent_type,
588+
source_value,
589+
field_group,
590+
field_path,
591+
incremental_data_record,
585592
)
586593
if result is Undefined:
587594
return results
@@ -607,7 +614,7 @@ def execute_fields(
607614
source_value: Any,
608615
path: Path | None,
609616
grouped_field_set: GroupedFieldSet,
610-
incremental_data_record: IncrementalDataRecord | None = None,
617+
incremental_data_record: IncrementalDataRecord,
611618
) -> AwaitableOrValue[dict[str, Any]]:
612619
"""Execute the given fields concurrently.
613620
@@ -662,7 +669,7 @@ def execute_field(
662669
source: Any,
663670
field_group: FieldGroup,
664671
path: Path,
665-
incremental_data_record: IncrementalDataRecord | None = None,
672+
incremental_data_record: IncrementalDataRecord,
666673
) -> AwaitableOrValue[Any]:
667674
"""Resolve the field on the given source object.
668675
@@ -774,7 +781,7 @@ def handle_field_error(
774781
return_type: GraphQLOutputType,
775782
field_group: FieldGroup,
776783
path: Path,
777-
incremental_data_record: IncrementalDataRecord | None = None,
784+
incremental_data_record: IncrementalDataRecord,
778785
) -> None:
779786
"""Handle error properly according to the field type."""
780787
error = located_error(raw_error, field_group, path.as_list())
@@ -784,13 +791,9 @@ def handle_field_error(
784791
if is_non_null_type(return_type):
785792
raise error
786793

787-
errors = (
788-
incremental_data_record.errors if incremental_data_record else self.errors
789-
)
790-
791794
# Otherwise, error protection is applied, logging the error and resolving a
792795
# null value for this field if one is encountered.
793-
errors.append(error)
796+
self.incremental_publisher.add_field_error(incremental_data_record, error)
794797

795798
def complete_value(
796799
self,
@@ -799,7 +802,7 @@ def complete_value(
799802
info: GraphQLResolveInfo,
800803
path: Path,
801804
result: Any,
802-
incremental_data_record: IncrementalDataRecord | None,
805+
incremental_data_record: IncrementalDataRecord,
803806
) -> AwaitableOrValue[Any]:
804807
"""Complete a value.
805808
@@ -888,7 +891,7 @@ async def complete_awaitable_value(
888891
info: GraphQLResolveInfo,
889892
path: Path,
890893
result: Any,
891-
incremental_data_record: IncrementalDataRecord | None = None,
894+
incremental_data_record: IncrementalDataRecord,
892895
) -> Any:
893896
"""Complete an awaitable value."""
894897
try:
@@ -955,7 +958,7 @@ async def complete_async_iterator_value(
955958
info: GraphQLResolveInfo,
956959
path: Path,
957960
async_iterator: AsyncIterator[Any],
958-
incremental_data_record: IncrementalDataRecord | None,
961+
incremental_data_record: IncrementalDataRecord,
959962
) -> list[Any]:
960963
"""Complete an async iterator.
961964
@@ -984,8 +987,8 @@ async def complete_async_iterator_value(
984987
info,
985988
item_type,
986989
path,
987-
stream.label,
988990
incremental_data_record,
991+
stream.label,
989992
)
990993
),
991994
timeout=ASYNC_DELAY,
@@ -1039,7 +1042,7 @@ def complete_list_value(
10391042
info: GraphQLResolveInfo,
10401043
path: Path,
10411044
result: AsyncIterable[Any] | Iterable[Any],
1042-
incremental_data_record: IncrementalDataRecord | None,
1045+
incremental_data_record: IncrementalDataRecord,
10431046
) -> AwaitableOrValue[list[Any]]:
10441047
"""Complete a list value.
10451048
@@ -1093,8 +1096,8 @@ def complete_list_value(
10931096
field_group,
10941097
info,
10951098
item_type,
1096-
stream.label,
10971099
previous_incremental_data_record,
1100+
stream.label,
10981101
)
10991102
continue
11001103

@@ -1138,7 +1141,7 @@ def complete_list_item_value(
11381141
field_group: FieldGroup,
11391142
info: GraphQLResolveInfo,
11401143
item_path: Path,
1141-
incremental_data_record: IncrementalDataRecord | None,
1144+
incremental_data_record: IncrementalDataRecord,
11421145
) -> bool:
11431146
"""Complete a list item value by adding it to the completed results.
11441147
@@ -1229,7 +1232,7 @@ def complete_abstract_value(
12291232
info: GraphQLResolveInfo,
12301233
path: Path,
12311234
result: Any,
1232-
incremental_data_record: IncrementalDataRecord | None,
1235+
incremental_data_record: IncrementalDataRecord,
12331236
) -> AwaitableOrValue[Any]:
12341237
"""Complete an abstract value.
12351238
@@ -1344,7 +1347,7 @@ def complete_object_value(
13441347
info: GraphQLResolveInfo,
13451348
path: Path,
13461349
result: Any,
1347-
incremental_data_record: IncrementalDataRecord | None,
1350+
incremental_data_record: IncrementalDataRecord,
13481351
) -> AwaitableOrValue[dict[str, Any]]:
13491352
"""Complete an Object value by executing all sub-selections."""
13501353
# If there is an `is_type_of()` predicate function, call it with the current
@@ -1379,7 +1382,7 @@ def collect_and_execute_subfields(
13791382
field_group: FieldGroup,
13801383
path: Path,
13811384
result: Any,
1382-
incremental_data_record: IncrementalDataRecord | None,
1385+
incremental_data_record: IncrementalDataRecord,
13831386
) -> AwaitableOrValue[dict[str, Any]]:
13841387
"""Collect sub-fields to execute to complete this value."""
13851388
sub_grouped_field_set, sub_patches = self.collect_subfields(
@@ -1396,9 +1399,9 @@ def collect_and_execute_subfields(
13961399
return_type,
13971400
result,
13981401
sub_patch_grouped_field_set,
1402+
incremental_data_record,
13991403
label,
14001404
path,
1401-
incremental_data_record,
14021405
)
14031406

14041407
return sub_fields
@@ -1474,9 +1477,9 @@ def execute_deferred_fragment(
14741477
parent_type: GraphQLObjectType,
14751478
source_value: Any,
14761479
fields: GroupedFieldSet,
1480+
parent_context: IncrementalDataRecord,
14771481
label: str | None = None,
14781482
path: Path | None = None,
1479-
parent_context: IncrementalDataRecord | None = None,
14801483
) -> None:
14811484
"""Execute deferred fragment."""
14821485
incremental_publisher = self.incremental_publisher
@@ -1529,9 +1532,9 @@ def execute_stream_field(
15291532
field_group: FieldGroup,
15301533
info: GraphQLResolveInfo,
15311534
item_type: GraphQLOutputType,
1535+
parent_context: IncrementalDataRecord,
15321536
label: str | None = None,
1533-
parent_context: IncrementalDataRecord | None = None,
1534-
) -> IncrementalDataRecord:
1537+
) -> SubsequentDataRecord:
15351538
"""Execute stream field."""
15361539
is_awaitable = self.is_awaitable
15371540
incremental_publisher = self.incremental_publisher
@@ -1678,8 +1681,8 @@ async def execute_stream_async_iterator(
16781681
info: GraphQLResolveInfo,
16791682
item_type: GraphQLOutputType,
16801683
path: Path,
1684+
parent_context: IncrementalDataRecord,
16811685
label: str | None = None,
1682-
parent_context: IncrementalDataRecord | None = None,
16831686
) -> None:
16841687
"""Execute stream iterator."""
16851688
incremental_publisher = self.incremental_publisher
@@ -1877,21 +1880,24 @@ def execute_impl(
18771880
# Errors from sub-fields of a NonNull type may propagate to the top level,
18781881
# at which point we still log the error and null the parent field, which
18791882
# in this case is the entire response.
1880-
errors = context.errors
18811883
incremental_publisher = context.incremental_publisher
1884+
initial_result_record = incremental_publisher.prepare_initial_result_record()
18821885
build_response = context.build_response
18831886
try:
1884-
result = context.execute_operation()
1887+
result = context.execute_operation(initial_result_record)
18851888

18861889
if context.is_awaitable(result):
18871890
# noinspection PyShadowingNames
18881891
async def await_result() -> Any:
18891892
try:
1893+
errors = incremental_publisher.get_initial_errors(
1894+
initial_result_record
1895+
)
18901896
initial_result = build_response(
18911897
await result, # type: ignore
18921898
errors,
18931899
)
1894-
incremental_publisher.publish_initial()
1900+
incremental_publisher.publish_initial(initial_result_record)
18951901
if incremental_publisher.has_next():
18961902
return ExperimentalIncrementalExecutionResults(
18971903
initial_result=InitialIncrementalExecutionResult(
@@ -1902,14 +1908,17 @@ async def await_result() -> Any:
19021908
subsequent_results=incremental_publisher.subscribe(),
19031909
)
19041910
except GraphQLError as error:
1905-
errors.append(error)
1911+
incremental_publisher.add_field_error(initial_result_record, error)
1912+
errors = incremental_publisher.get_initial_errors(
1913+
initial_result_record
1914+
)
19061915
return build_response(None, errors)
19071916
return initial_result
19081917

19091918
return await_result()
19101919

1911-
initial_result = build_response(result, errors) # type: ignore
1912-
incremental_publisher.publish_initial()
1920+
initial_result = build_response(result, initial_result_record.errors) # type: ignore
1921+
incremental_publisher.publish_initial(initial_result_record)
19131922
if incremental_publisher.has_next():
19141923
return ExperimentalIncrementalExecutionResults(
19151924
initial_result=InitialIncrementalExecutionResult(
@@ -1920,7 +1929,8 @@ async def await_result() -> Any:
19201929
subsequent_results=incremental_publisher.subscribe(),
19211930
)
19221931
except GraphQLError as error:
1923-
errors.append(error)
1932+
incremental_publisher.add_field_error(initial_result_record, error)
1933+
errors = incremental_publisher.get_initial_errors(initial_result_record)
19241934
return build_response(None, errors)
19251935
return initial_result
19261936

0 commit comments

Comments
 (0)