Skip to content

Commit efa9e82

Browse files
mawaCito
authored andcommitted
Await resolvers in parallel (#11)
1 parent 3de6454 commit efa9e82

File tree

4 files changed

+315
-237
lines changed

4 files changed

+315
-237
lines changed

graphql/execution/execute.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from asyncio import gather
12
from inspect import isawaitable
23
from typing import (
34
Any,
@@ -437,12 +438,17 @@ def execute_fields(
437438
# Otherwise, results is a map from field name to the result of
438439
# resolving that field, which is possibly a coroutine object.
439440
# Return a coroutine object that will yield this same map, but with
440-
# any coroutines awaited and replaced with the values they yielded.
441+
# any coroutines awaited in parallel and replaced with the values they
442+
# yielded.
441443
async def get_results():
442-
return {
443-
key: await value if isawaitable(value) else value
444-
for key, value in results.items()
445-
}
444+
async def await_kv(key, value):
445+
return key, await value if isawaitable(value) else value
446+
447+
pairs = await gather(
448+
*(await_kv(key, value) for key, value in results.items())
449+
)
450+
451+
return dict(pairs)
446452

447453
return get_results()
448454

tests/execution/test_executor.py

Lines changed: 102 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
GraphQLResolveInfo,
2121
ResponsePath,
2222
)
23+
from .util import compare_query_results_unordered
2324

2425

2526
def describe_execute_handles_basic_execution_tasks():
@@ -417,67 +418,70 @@ async def asyncReturnErrorWithExtensions(self, _info):
417418
)
418419
)
419420

420-
assert await execute(schema, ast, Data()) == (
421-
{
422-
"syncOk": "sync ok",
423-
"syncError": None,
424-
"syncRawError": None,
425-
"syncReturnError": None,
426-
"syncReturnErrorList": ["sync0", None, "sync2", None],
427-
"asyncOk": "async ok",
428-
"asyncError": None,
429-
"asyncRawError": None,
430-
"asyncReturnError": None,
431-
"asyncReturnErrorWithExtensions": None,
432-
},
433-
[
434-
{
435-
"message": "Error getting syncError",
436-
"locations": [(3, 15)],
437-
"path": ["syncError"],
438-
},
439-
{
440-
"message": "Error getting syncRawError",
441-
"locations": [(4, 15)],
442-
"path": ["syncRawError"],
443-
},
444-
{
445-
"message": "Error getting syncReturnError",
446-
"locations": [(5, 15)],
447-
"path": ["syncReturnError"],
448-
},
449-
{
450-
"message": "Error getting syncReturnErrorList1",
451-
"locations": [(6, 15)],
452-
"path": ["syncReturnErrorList", 1],
453-
},
454-
{
455-
"message": "Error getting syncReturnErrorList3",
456-
"locations": [(6, 15)],
457-
"path": ["syncReturnErrorList", 3],
458-
},
459-
{
460-
"message": "Error getting asyncError",
461-
"locations": [(8, 15)],
462-
"path": ["asyncError"],
463-
},
464-
{
465-
"message": "Error getting asyncRawError",
466-
"locations": [(9, 15)],
467-
"path": ["asyncRawError"],
468-
},
469-
{
470-
"message": "Error getting asyncReturnError",
471-
"locations": [(10, 15)],
472-
"path": ["asyncReturnError"],
473-
},
421+
compare_query_results_unordered(
422+
await execute(schema, ast, Data()),
423+
(
474424
{
475-
"message": "Error getting asyncReturnErrorWithExtensions",
476-
"locations": [(11, 15)],
477-
"path": ["asyncReturnErrorWithExtensions"],
478-
"extensions": {"foo": "bar"},
425+
"syncOk": "sync ok",
426+
"syncError": None,
427+
"syncRawError": None,
428+
"syncReturnError": None,
429+
"syncReturnErrorList": ["sync0", None, "sync2", None],
430+
"asyncOk": "async ok",
431+
"asyncError": None,
432+
"asyncRawError": None,
433+
"asyncReturnError": None,
434+
"asyncReturnErrorWithExtensions": None,
479435
},
480-
],
436+
[
437+
{
438+
"message": "Error getting syncError",
439+
"locations": [(3, 15)],
440+
"path": ["syncError"],
441+
},
442+
{
443+
"message": "Error getting syncRawError",
444+
"locations": [(4, 15)],
445+
"path": ["syncRawError"],
446+
},
447+
{
448+
"message": "Error getting syncReturnError",
449+
"locations": [(5, 15)],
450+
"path": ["syncReturnError"],
451+
},
452+
{
453+
"message": "Error getting syncReturnErrorList1",
454+
"locations": [(6, 15)],
455+
"path": ["syncReturnErrorList", 1],
456+
},
457+
{
458+
"message": "Error getting syncReturnErrorList3",
459+
"locations": [(6, 15)],
460+
"path": ["syncReturnErrorList", 3],
461+
},
462+
{
463+
"message": "Error getting asyncError",
464+
"locations": [(8, 15)],
465+
"path": ["asyncError"],
466+
},
467+
{
468+
"message": "Error getting asyncRawError",
469+
"locations": [(9, 15)],
470+
"path": ["asyncRawError"],
471+
},
472+
{
473+
"message": "Error getting asyncReturnError",
474+
"locations": [(10, 15)],
475+
"path": ["asyncReturnError"],
476+
},
477+
{
478+
"message": "Error getting asyncReturnErrorWithExtensions",
479+
"locations": [(11, 15)],
480+
"path": ["asyncReturnErrorWithExtensions"],
481+
"extensions": {"foo": "bar"},
482+
},
483+
],
484+
),
481485
)
482486

483487
def full_response_path_is_included_for_non_nullable_fields():
@@ -866,3 +870,42 @@ def resolve_field(self, parent_type, source, field_nodes, path):
866870
{"foo": "barbar"},
867871
None,
868872
)
873+
874+
@mark.asyncio
875+
async def resolve_fields_in_parallel():
876+
class Barrier(object):
877+
# Makes progress only if at least `count` callers are `wait()`ing.
878+
def __init__(self, count):
879+
self.ev = asyncio.Event()
880+
self.count = count
881+
882+
async def wait(self):
883+
self.count -= 1
884+
if self.count == 0:
885+
self.ev.set()
886+
887+
return await self.ev.wait()
888+
889+
barrier = Barrier(2)
890+
891+
async def f(*args):
892+
return await barrier.wait()
893+
894+
schema = GraphQLSchema(
895+
GraphQLObjectType(
896+
"Object",
897+
{
898+
"foo": GraphQLField(GraphQLBoolean, resolve=f),
899+
"bar": GraphQLField(GraphQLBoolean, resolve=f),
900+
},
901+
)
902+
)
903+
904+
query = "{foo, bar}"
905+
ast = parse(query)
906+
907+
res = await asyncio.wait_for(
908+
execute(schema, ast), 1.0 # don't wait forever for the test to fail
909+
)
910+
911+
assert res == ({"foo": True, "bar": True}, None)

0 commit comments

Comments
 (0)