Skip to content

Commit c671d83

Browse files
committed
fix: asynchronously gather execution results
instead of awaiting them sequentially
1 parent 184ba72 commit c671d83

File tree

4 files changed

+61
-17
lines changed

4 files changed

+61
-17
lines changed

graphql_server/aiohttp/graphqlview.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import asyncio
12
import copy
23
from collections.abc import MutableMapping
34
from functools import partial
45
from typing import List
56

67
from aiohttp import web
7-
from graphql import ExecutionResult, GraphQLError, specified_rules
8+
from graphql import GraphQLError, specified_rules
9+
from graphql.pyutils import is_awaitable
810
from graphql.type.schema import GraphQLSchema
911

1012
from graphql_server import (
@@ -22,6 +24,7 @@
2224
GraphiQLOptions,
2325
render_graphiql_async,
2426
)
27+
from graphql_server.utils import ensure_async
2528

2629

2730
class GraphQLView:
@@ -161,10 +164,14 @@ async def __call__(self, request):
161164
)
162165

163166
exec_res = (
164-
[
165-
ex if ex is None or isinstance(ex, ExecutionResult) else await ex
166-
for ex in execution_results
167-
]
167+
await asyncio.gather(
168+
*(
169+
ex
170+
if ex is not None and is_awaitable(ex)
171+
else ensure_async(lambda: ex)()
172+
for ex in execution_results
173+
)
174+
)
168175
if self.enable_async
169176
else execution_results
170177
)

graphql_server/quart/graphqlview.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import asyncio
12
import copy
23
from collections.abc import MutableMapping
34
from functools import partial
45
from typing import List
56

6-
from graphql import ExecutionResult, specified_rules
7+
from graphql import specified_rules
78
from graphql.error import GraphQLError
9+
from graphql.pyutils import is_awaitable
810
from graphql.type.schema import GraphQLSchema
911
from quart import Response, render_template_string, request
1012
from quart.views import View
@@ -24,6 +26,7 @@
2426
GraphiQLOptions,
2527
render_graphiql_sync,
2628
)
29+
from graphql_server.utils import ensure_async
2730

2831

2932
class GraphQLView(View):
@@ -108,10 +111,14 @@ async def dispatch_request(self):
108111
validation_rules=self.get_validation_rules(),
109112
)
110113
exec_res = (
111-
[
112-
ex if ex is None or isinstance(ex, ExecutionResult) else await ex
113-
for ex in execution_results
114-
]
114+
await asyncio.gather(
115+
*(
116+
ex
117+
if ex is not None and is_awaitable(ex)
118+
else ensure_async(lambda: ex)()
119+
for ex in execution_results
120+
)
121+
)
115122
if self.enable_async
116123
else execution_results
117124
)

graphql_server/sanic/graphqlview.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import asyncio
12
import copy
23
from cgi import parse_header
34
from collections.abc import MutableMapping
45
from functools import partial
56
from typing import List
67

7-
from graphql import ExecutionResult, GraphQLError, specified_rules
8+
from graphql import GraphQLError, specified_rules
9+
from graphql.pyutils import is_awaitable
810
from graphql.type.schema import GraphQLSchema
911
from sanic.response import HTTPResponse, html
1012
from sanic.views import HTTPMethodView
@@ -24,6 +26,7 @@
2426
GraphiQLOptions,
2527
render_graphiql_async,
2628
)
29+
from graphql_server.utils import ensure_async
2730

2831

2932
class GraphQLView(HTTPMethodView):
@@ -114,12 +117,14 @@ async def __handle_request(self, request, *args, **kwargs):
114117
validation_rules=self.get_validation_rules(),
115118
)
116119
exec_res = (
117-
[
118-
ex
119-
if ex is None or isinstance(ex, ExecutionResult)
120-
else await ex
121-
for ex in execution_results
122-
]
120+
await asyncio.gather(
121+
*(
122+
ex
123+
if ex is not None and is_awaitable(ex)
124+
else ensure_async(lambda: ex)()
125+
for ex in execution_results
126+
)
127+
)
123128
if self.enable_async
124129
else execution_results
125130
)

graphql_server/utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import sys
2+
from typing import Awaitable, Callable, TypeVar
3+
4+
if sys.version_info >= (3, 10):
5+
from typing import ParamSpec
6+
else:
7+
from typing_extensions import ParamSpec
8+
9+
10+
__all__ = ["ensure_async"]
11+
12+
P = ParamSpec("P")
13+
R = TypeVar("R")
14+
15+
16+
def ensure_async(f: Callable[P, R]) -> Callable[P, Awaitable[R]]:
17+
"""Convert a sync callable (normal def or lambda) to a coroutine (async def).
18+
19+
This is similar to asyncio.coroutine which was deprecated in Python 3.8.
20+
"""
21+
22+
async def f_async(*args: P.args, **kwargs: P.kwargs) -> R:
23+
return f(*args, **kwargs)
24+
25+
return f_async

0 commit comments

Comments
 (0)