diff --git a/graphql/execution/execute.py b/graphql/execution/execute.py index f60b00e3..7114655d 100644 --- a/graphql/execution/execute.py +++ b/graphql/execution/execute.py @@ -1,3 +1,4 @@ +from asyncio import gather from inspect import isawaitable from typing import ( Any, @@ -437,12 +438,17 @@ def execute_fields( # Otherwise, results is a map from field name to the result of # resolving that field, which is possibly a coroutine object. # Return a coroutine object that will yield this same map, but with - # any coroutines awaited and replaced with the values they yielded. + # any coroutines awaited in parallel and replaced with the values they + # yielded. async def get_results(): - return { - key: await value if isawaitable(value) else value - for key, value in results.items() - } + async def await_kv(key, value): + return key, await value if isawaitable(value) else value + + pairs = await gather( + *(await_kv(key, value) for key, value in results.items()) + ) + + return dict(pairs) return get_results() diff --git a/tests/execution/test_executor.py b/tests/execution/test_executor.py index 1ec4f286..5cedd309 100644 --- a/tests/execution/test_executor.py +++ b/tests/execution/test_executor.py @@ -20,6 +20,7 @@ GraphQLResolveInfo, ResponsePath, ) +from .util import compare_query_results_unordered def describe_execute_handles_basic_execution_tasks(): @@ -417,67 +418,70 @@ async def asyncReturnErrorWithExtensions(self, _info): ) ) - assert await execute(schema, ast, Data()) == ( - { - "syncOk": "sync ok", - "syncError": None, - "syncRawError": None, - "syncReturnError": None, - "syncReturnErrorList": ["sync0", None, "sync2", None], - "asyncOk": "async ok", - "asyncError": None, - "asyncRawError": None, - "asyncReturnError": None, - "asyncReturnErrorWithExtensions": None, - }, - [ - { - "message": "Error getting syncError", - "locations": [(3, 15)], - "path": ["syncError"], - }, - { - "message": "Error getting syncRawError", - "locations": [(4, 15)], - "path": ["syncRawError"], - }, - { - "message": "Error getting syncReturnError", - "locations": [(5, 15)], - "path": ["syncReturnError"], - }, - { - "message": "Error getting syncReturnErrorList1", - "locations": [(6, 15)], - "path": ["syncReturnErrorList", 1], - }, - { - "message": "Error getting syncReturnErrorList3", - "locations": [(6, 15)], - "path": ["syncReturnErrorList", 3], - }, - { - "message": "Error getting asyncError", - "locations": [(8, 15)], - "path": ["asyncError"], - }, - { - "message": "Error getting asyncRawError", - "locations": [(9, 15)], - "path": ["asyncRawError"], - }, - { - "message": "Error getting asyncReturnError", - "locations": [(10, 15)], - "path": ["asyncReturnError"], - }, + compare_query_results_unordered( + await execute(schema, ast, Data()), + ( { - "message": "Error getting asyncReturnErrorWithExtensions", - "locations": [(11, 15)], - "path": ["asyncReturnErrorWithExtensions"], - "extensions": {"foo": "bar"}, + "syncOk": "sync ok", + "syncError": None, + "syncRawError": None, + "syncReturnError": None, + "syncReturnErrorList": ["sync0", None, "sync2", None], + "asyncOk": "async ok", + "asyncError": None, + "asyncRawError": None, + "asyncReturnError": None, + "asyncReturnErrorWithExtensions": None, }, - ], + [ + { + "message": "Error getting syncError", + "locations": [(3, 15)], + "path": ["syncError"], + }, + { + "message": "Error getting syncRawError", + "locations": [(4, 15)], + "path": ["syncRawError"], + }, + { + "message": "Error getting syncReturnError", + "locations": [(5, 15)], + "path": ["syncReturnError"], + }, + { + "message": "Error getting syncReturnErrorList1", + "locations": [(6, 15)], + "path": ["syncReturnErrorList", 1], + }, + { + "message": "Error getting syncReturnErrorList3", + "locations": [(6, 15)], + "path": ["syncReturnErrorList", 3], + }, + { + "message": "Error getting asyncError", + "locations": [(8, 15)], + "path": ["asyncError"], + }, + { + "message": "Error getting asyncRawError", + "locations": [(9, 15)], + "path": ["asyncRawError"], + }, + { + "message": "Error getting asyncReturnError", + "locations": [(10, 15)], + "path": ["asyncReturnError"], + }, + { + "message": "Error getting asyncReturnErrorWithExtensions", + "locations": [(11, 15)], + "path": ["asyncReturnErrorWithExtensions"], + "extensions": {"foo": "bar"}, + }, + ], + ), ) def full_response_path_is_included_for_non_nullable_fields(): @@ -893,19 +897,15 @@ async def f(*args): { "foo": GraphQLField(GraphQLBoolean, resolve=f), "bar": GraphQLField(GraphQLBoolean, resolve=f), - } + }, ) ) - query = '{foo, bar}' + query = "{foo, bar}" ast = parse(query) res = await asyncio.wait_for( - execute(schema, ast), - 1.0, # don't wait forever for the test to fail + execute(schema, ast), 1.0 # don't wait forever for the test to fail ) - assert res == ( - {"foo": True, "bar": True}, - None, - ) + assert res == ({"foo": True, "bar": True}, None) diff --git a/tests/execution/test_nonnull.py b/tests/execution/test_nonnull.py index 42208dd0..bdafe555 100644 --- a/tests/execution/test_nonnull.py +++ b/tests/execution/test_nonnull.py @@ -13,6 +13,8 @@ GraphQLString, ) +from .util import compare_query_results_unordered + sync_error = RuntimeError("sync") sync_non_null_error = RuntimeError("syncNonNull") promise_error = RuntimeError("promise") @@ -254,70 +256,73 @@ async def returns_null(): @mark.asyncio async def throws(): result = await execute_query(query, ThrowingData()) - assert result == ( - data, - [ - { - "message": str(sync_error), - "path": ["syncNest", "sync"], - "locations": [(4, 17)], - }, - { - "message": str(sync_error), - "path": ["syncNest", "syncNest", "sync"], - "locations": [(6, 28)], - }, - { - "message": str(promise_error), - "path": ["syncNest", "promise"], - "locations": [(5, 17)], - }, - { - "message": str(promise_error), - "path": ["syncNest", "syncNest", "promise"], - "locations": [(6, 33)], - }, - { - "message": str(sync_error), - "path": ["syncNest", "promiseNest", "sync"], - "locations": [(7, 31)], - }, - { - "message": str(promise_error), - "path": ["syncNest", "promiseNest", "promise"], - "locations": [(7, 36)], - }, - { - "message": str(sync_error), - "path": ["promiseNest", "sync"], - "locations": [(10, 17)], - }, - { - "message": str(sync_error), - "path": ["promiseNest", "syncNest", "sync"], - "locations": [(12, 28)], - }, - { - "message": str(promise_error), - "path": ["promiseNest", "promise"], - "locations": [(11, 17)], - }, - { - "message": str(promise_error), - "path": ["promiseNest", "syncNest", "promise"], - "locations": [(12, 33)], - }, - { - "message": str(sync_error), - "path": ["promiseNest", "promiseNest", "sync"], - "locations": [(13, 31)], - }, - { - "message": str(promise_error), - "path": ["promiseNest", "promiseNest", "promise"], - "locations": [(13, 36)], - }, - ], + compare_query_results_unordered( + result, + ( + data, + [ + { + "message": str(sync_error), + "path": ["syncNest", "sync"], + "locations": [(4, 17)], + }, + { + "message": str(sync_error), + "path": ["syncNest", "syncNest", "sync"], + "locations": [(6, 28)], + }, + { + "message": str(promise_error), + "path": ["syncNest", "promise"], + "locations": [(5, 17)], + }, + { + "message": str(promise_error), + "path": ["syncNest", "syncNest", "promise"], + "locations": [(6, 33)], + }, + { + "message": str(sync_error), + "path": ["syncNest", "promiseNest", "sync"], + "locations": [(7, 31)], + }, + { + "message": str(promise_error), + "path": ["syncNest", "promiseNest", "promise"], + "locations": [(7, 36)], + }, + { + "message": str(sync_error), + "path": ["promiseNest", "sync"], + "locations": [(10, 17)], + }, + { + "message": str(sync_error), + "path": ["promiseNest", "syncNest", "sync"], + "locations": [(12, 28)], + }, + { + "message": str(promise_error), + "path": ["promiseNest", "promise"], + "locations": [(11, 17)], + }, + { + "message": str(promise_error), + "path": ["promiseNest", "syncNest", "promise"], + "locations": [(12, 33)], + }, + { + "message": str(sync_error), + "path": ["promiseNest", "promiseNest", "sync"], + "locations": [(13, 31)], + }, + { + "message": str(promise_error), + "path": ["promiseNest", "promiseNest", "promise"], + "locations": [(13, 36)], + }, + ], + ), ) def describe_nulls_first_nullable_after_long_chain_of_non_null_fields(): @@ -378,120 +383,125 @@ def describe_nulls_first_nullable_after_long_chain_of_non_null_fields(): @mark.asyncio async def returns_null(): - result = await execute_query(query, NullingData()) - assert result == ( - data, - [ - { - "message": "Cannot return null for non-nullable field" - " DataType.syncNonNull.", - "path": [ - "syncNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNull", - ], - "locations": [(8, 25)], - }, - { - "message": "Cannot return null for non-nullable field" - " DataType.syncNonNull.", - "path": [ - "promiseNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNull", - ], - "locations": [(19, 25)], - }, - { - "message": "Cannot return null for non-nullable field" - " DataType.promiseNonNull.", - "path": [ - "anotherNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "promiseNonNull", - ], - "locations": [(30, 25)], - }, - { - "message": "Cannot return null for non-nullable field" - " DataType.promiseNonNull.", - "path": [ - "anotherPromiseNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "promiseNonNull", - ], - "locations": [(41, 25)], - }, - ], + compare_query_results_unordered( + await execute_query(query, NullingData()), + ( + data, + [ + { + "message": "Cannot return null for non-nullable field" + " DataType.syncNonNull.", + "path": [ + "syncNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNull", + ], + "locations": [(8, 25)], + }, + { + "message": "Cannot return null for non-nullable field" + " DataType.syncNonNull.", + "path": [ + "promiseNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNull", + ], + "locations": [(19, 25)], + }, + { + "message": "Cannot return null for non-nullable field" + " DataType.promiseNonNull.", + "path": [ + "anotherNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "promiseNonNull", + ], + "locations": [(30, 25)], + }, + { + "message": "Cannot return null for non-nullable field" + " DataType.promiseNonNull.", + "path": [ + "anotherPromiseNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "promiseNonNull", + ], + "locations": [(41, 25)], + }, + ], + ), ) @mark.asyncio async def throws(): result = await execute_query(query, ThrowingData()) - assert result == ( - data, - [ - { - "message": str(sync_non_null_error), - "path": [ - "syncNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNull", - ], - "locations": [(8, 25)], - }, - { - "message": str(sync_non_null_error), - "path": [ - "promiseNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNull", - ], - "locations": [(19, 25)], - }, - { - "message": str(promise_non_null_error), - "path": [ - "anotherNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "promiseNonNull", - ], - "locations": [(30, 25)], - }, - { - "message": str(promise_non_null_error), - "path": [ - "anotherPromiseNest", - "syncNonNullNest", - "promiseNonNullNest", - "syncNonNullNest", - "promiseNonNullNest", - "promiseNonNull", - ], - "locations": [(41, 25)], - }, - ], + compare_query_results_unordered( + result, + ( + data, + [ + { + "message": str(sync_non_null_error), + "path": [ + "syncNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNull", + ], + "locations": [(8, 25)], + }, + { + "message": str(sync_non_null_error), + "path": [ + "promiseNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNull", + ], + "locations": [(19, 25)], + }, + { + "message": str(promise_non_null_error), + "path": [ + "anotherNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "promiseNonNull", + ], + "locations": [(30, 25)], + }, + { + "message": str(promise_non_null_error), + "path": [ + "anotherPromiseNest", + "syncNonNullNest", + "promiseNonNullNest", + "syncNonNullNest", + "promiseNonNullNest", + "promiseNonNull", + ], + "locations": [(41, 25)], + }, + ], + ), ) def describe_nulls_the_top_level_if_non_nullable_field(): diff --git a/tests/execution/util.py b/tests/execution/util.py new file mode 100644 index 00000000..b207e1ff --- /dev/null +++ b/tests/execution/util.py @@ -0,0 +1,19 @@ +def compare_query_results_unordered(data, reference): + def err_to_dict(exception): + exception_dict = { + "message": exception.message, + "path": exception.path, + "locations": exception.locations, + } + + extensions = exception.extensions + if extensions: + exception_dict["extensions"] = extensions + + return exception_dict + + def key(e): + return e["path"] + + assert data[0] == reference[0] + assert sorted(map(err_to_dict, data[1]), key=key) == sorted(reference[1], key=key)