Skip to content

Commit 29034c2

Browse files
committed
execute: Correctly report missing root type error
Replicates graphql/graphql-js@2aad6b4
1 parent c37661c commit 29034c2

File tree

2 files changed

+65
-54
lines changed

2 files changed

+65
-54
lines changed

src/graphql/execution/execute.py

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -307,22 +307,15 @@ def build(
307307
is_awaitable,
308308
)
309309

310+
@staticmethod
310311
def build_response(
311-
self, data: AwaitableOrValue[Optional[Dict[str, Any]]]
312-
) -> AwaitableOrValue[ExecutionResult]:
312+
data: Optional[Dict[str, Any]], errors: List[GraphQLError]
313+
) -> ExecutionResult:
313314
"""Build response.
314315
315316
Given a completed execution context and data, build the (data, errors) response
316317
defined by the "Response" section of the GraphQL spec.
317318
"""
318-
if self.is_awaitable(data):
319-
320-
async def build_response_async() -> ExecutionResult:
321-
return self.build_response(await data) # type: ignore
322-
323-
return build_response_async()
324-
data = cast(Optional[Dict[str, Any]], data)
325-
errors = self.errors
326319
if not errors:
327320
return ExecutionResult(data, None)
328321
# Sort the error list in order to make it deterministic, since we might have
@@ -357,30 +350,11 @@ def execute_operation(
357350

358351
path = None
359352

360-
# Errors from sub-fields of a NonNull type may propagate to the top level, at
361-
# which point we still log the error and null the parent field, which in this
362-
# case is the entire response.
363-
try:
364-
# noinspection PyArgumentList
365-
result = (
366-
self.execute_fields_serially
367-
if operation.operation == OperationType.MUTATION
368-
else self.execute_fields
369-
)(root_type, root_value, path, root_fields)
370-
except GraphQLError as error:
371-
self.errors.append(error)
372-
return None
373-
else:
374-
if self.is_awaitable(result):
375-
# noinspection PyShadowingNames
376-
async def await_result() -> Any:
377-
try:
378-
return await result # type: ignore
379-
except GraphQLError as error:
380-
self.errors.append(error)
381-
382-
return await_result()
383-
return result
353+
return (
354+
self.execute_fields_serially
355+
if operation.operation == OperationType.MUTATION
356+
else self.execute_fields
357+
)(root_type, root_value, path, root_fields)
384358

385359
def execute_fields_serially(
386360
self,
@@ -815,6 +789,7 @@ def complete_abstract_value(
815789
runtime_type = resolve_type_fn(result, info, return_type) # type: ignore
816790

817791
if self.is_awaitable(runtime_type):
792+
runtime_type = cast(Awaitable, runtime_type)
818793

819794
async def await_complete_object_value() -> Any:
820795
value = self.complete_object_value(
@@ -1037,9 +1012,31 @@ def execute(
10371012
# its descendants will be omitted, and sibling fields will still be executed. An
10381013
# execution which encounters errors will still result in a coroutine object that
10391014
# can be executed without errors.
1040-
1041-
data = exe_context.execute_operation(exe_context.operation, root_value)
1042-
return exe_context.build_response(data)
1015+
#
1016+
# Errors from sub-fields of a NonNull type may propagate to the top level,
1017+
# at which point we still log the error and null the parent field, which
1018+
# in this case is the entire response.
1019+
errors = exe_context.errors
1020+
build_response = exe_context.build_response
1021+
try:
1022+
operation = exe_context.operation
1023+
result = exe_context.execute_operation(operation, root_value)
1024+
1025+
if exe_context.is_awaitable(result):
1026+
# noinspection PyShadowingNames
1027+
async def await_result() -> Any:
1028+
try:
1029+
return build_response(await result, errors) # type: ignore
1030+
except GraphQLError as error:
1031+
errors.append(error)
1032+
return build_response(None, errors)
1033+
1034+
return await_result()
1035+
except GraphQLError as error:
1036+
errors.append(error)
1037+
return build_response(None, errors)
1038+
else:
1039+
return build_response(result, errors) # type: ignore
10431040

10441041

10451042
def assume_not_awaitable(_value: Any) -> bool:

tests/execution/test_executor.py

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -800,23 +800,37 @@ def resolves_to_an_error_if_schema_does_not_support_operation():
800800
"""
801801
)
802802

803-
with raises(
804-
GraphQLError,
805-
match=r"^Schema is not configured to execute query operation\.",
806-
):
807-
execute_sync(schema, document, operation_name="Q")
808-
809-
with raises(
810-
GraphQLError,
811-
match=r"^Schema is not configured to execute mutation operation\.",
812-
):
813-
execute_sync(schema, document, operation_name="M")
814-
815-
with raises(
816-
GraphQLError,
817-
match=r"^Schema is not configured to execute subscription operation\.",
818-
):
819-
execute_sync(schema, document, operation_name="S")
803+
assert execute_sync(schema, document, operation_name="Q") == (
804+
None,
805+
[
806+
{
807+
"message": "Schema is not configured to execute query operation.",
808+
"locations": [(2, 13)],
809+
}
810+
],
811+
)
812+
813+
assert execute_sync(schema, document, operation_name="M") == (
814+
None,
815+
[
816+
{
817+
"message": "Schema is not configured to execute"
818+
" mutation operation.",
819+
"locations": [(3, 13)],
820+
}
821+
],
822+
)
823+
824+
assert execute_sync(schema, document, operation_name="S") == (
825+
None,
826+
[
827+
{
828+
"message": "Schema is not configured to execute"
829+
" subscription operation.",
830+
"locations": [(4, 13)],
831+
}
832+
],
833+
)
820834

821835
@mark.asyncio
822836
async def correct_field_ordering_despite_execution_order():

0 commit comments

Comments
 (0)