Skip to content

Commit 89e95e6

Browse files
committed
Improved asyncio implementation. Fixed graphql-python#63
1 parent ed27ce0 commit 89e95e6

File tree

2 files changed

+118
-2
lines changed

2 files changed

+118
-2
lines changed

graphql/execution/executors/asyncio.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
from __future__ import absolute_import
22

3-
from asyncio import Future, ensure_future, get_event_loop, iscoroutine, wait
3+
from asyncio import Future, get_event_loop, iscoroutine, wait
4+
5+
try:
6+
from asyncio import ensure_future
7+
except ImportError:
8+
# ensure_future is only implemented in Python 3.4.4+
9+
def ensure_future(coro_or_future, loop=None):
10+
"""Wrap a coroutine or an awaitable in a future.
11+
12+
If the argument is a Future, it is returned directly.
13+
"""
14+
if isinstance(coro_or_future, Future):
15+
if loop is not None and loop is not coro_or_future._loop:
16+
raise ValueError('loop argument must agree with Future')
17+
return coro_or_future
18+
elif iscoroutine(coro_or_future):
19+
if loop is None:
20+
loop = get_event_loop()
21+
task = loop.create_task(coro_or_future)
22+
if task._source_traceback:
23+
del task._source_traceback[-1]
24+
return task
25+
else:
26+
raise TypeError('A Future, a coroutine or an awaitable is required')
427

528

629
class AsyncioExecutor(object):
@@ -20,7 +43,7 @@ def wait_until_finished(self):
2043
def execute(self, fn, *args, **kwargs):
2144
result = fn(*args, **kwargs)
2245
if isinstance(result, Future) or iscoroutine(result):
23-
future = ensure_future(result)
46+
future = ensure_future(result, loop=self.loop)
2447
self.futures.append(future)
2548
return future
2649
return result
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
isort:skip_file
3+
"""
4+
# flake8: noqa
5+
import pytest
6+
asyncio = pytest.importorskip("asyncio")
7+
8+
from graphql.error import format_error
9+
from graphql.execution import execute
10+
from graphql.language.parser import parse
11+
from graphql.type import (GraphQLField, GraphQLObjectType, GraphQLSchema,
12+
GraphQLString)
13+
14+
from ..executors.asyncio import AsyncioExecutor
15+
from .test_mutations import assert_evaluate_mutations_serially
16+
17+
18+
def test_asyncio_executor():
19+
def resolver(context, *_):
20+
asyncio.sleep(0.001)
21+
return 'hey'
22+
23+
@asyncio.coroutine
24+
def resolver_2(context, *_):
25+
asyncio.sleep(0.003)
26+
return 'hey2'
27+
28+
def resolver_3(contest, *_):
29+
return 'hey3'
30+
31+
Type = GraphQLObjectType('Type', {
32+
'a': GraphQLField(GraphQLString, resolver=resolver),
33+
'b': GraphQLField(GraphQLString, resolver=resolver_2),
34+
'c': GraphQLField(GraphQLString, resolver=resolver_3)
35+
})
36+
37+
ast = parse('{ a b c }')
38+
result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor())
39+
assert not result.errors
40+
assert result.data == {'a': 'hey', 'b': 'hey2', 'c': 'hey3'}
41+
42+
43+
def test_asyncio_executor_custom_loop():
44+
loop = asyncio.get_event_loop()
45+
46+
def resolver(context, *_):
47+
asyncio.sleep(0.001, loop=loop)
48+
return 'hey'
49+
50+
@asyncio.coroutine
51+
def resolver_2(context, *_):
52+
asyncio.sleep(0.003, loop=loop)
53+
return 'hey2'
54+
55+
def resolver_3(contest, *_):
56+
return 'hey3'
57+
58+
Type = GraphQLObjectType('Type', {
59+
'a': GraphQLField(GraphQLString, resolver=resolver),
60+
'b': GraphQLField(GraphQLString, resolver=resolver_2),
61+
'c': GraphQLField(GraphQLString, resolver=resolver_3)
62+
})
63+
64+
ast = parse('{ a b c }')
65+
result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor(loop=loop))
66+
assert not result.errors
67+
assert result.data == {'a': 'hey', 'b': 'hey2', 'c': 'hey3'}
68+
69+
70+
def test_asyncio_executor_with_error():
71+
ast = parse('query Example { a, b }')
72+
73+
def resolver(context, *_):
74+
asyncio.sleep(0.001)
75+
return 'hey'
76+
77+
def resolver_2(context, *_):
78+
asyncio.sleep(0.003)
79+
raise Exception('resolver_2 failed!')
80+
81+
Type = GraphQLObjectType('Type', {
82+
'a': GraphQLField(GraphQLString, resolver=resolver),
83+
'b': GraphQLField(GraphQLString, resolver=resolver_2)
84+
})
85+
86+
result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor())
87+
formatted_errors = list(map(format_error, result.errors))
88+
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
89+
assert result.data == {'a': 'hey', 'b': None}
90+
91+
92+
def test_evaluates_mutations_serially():
93+
assert_evaluate_mutations_serially(executor=AsyncioExecutor())

0 commit comments

Comments
 (0)