Skip to content

Commit 7d4bc99

Browse files
committed
Improved serial resolver. Added mutations and variables tests
1 parent 45e0a14 commit 7d4bc99

File tree

4 files changed

+838
-7
lines changed

4 files changed

+838
-7
lines changed

graphql/execution/querybuilder/executor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,7 @@ def execute_operation(exe_context, operation, root_value):
7676
type = get_operation_root_type(exe_context.schema, operation)
7777
execute_serially = operation.operation == 'mutation'
7878

79-
fragment = Fragment(type=type, selection_set=operation.selection_set, context=exe_context, execute_serially=execute_serially)
79+
fragment = Fragment(type=type, selection_set=operation.selection_set, context=exe_context)
80+
if execute_serially:
81+
return fragment.resolve_serially(root_value)
8082
return fragment.resolve(root_value)

graphql/execution/querybuilder/fragment.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
from promise import promise_for_dict
1+
from promise import promise_for_dict, Promise
22

3+
import functools
34
from functools import partial
45
from ...pyutils.cached_property import cached_property
56
from ..values import get_argument_values, get_variable_values
@@ -73,10 +74,9 @@ def get_resolvers(context, type, selection_set):
7374

7475

7576
class Fragment(object):
76-
def __init__(self, type, selection_set, context=None, execute_serially=False):
77+
def __init__(self, type, selection_set, context=None):
7778
self.type = type
7879
self.selection_set = selection_set
79-
self.execute_serially = execute_serially
8080
self.context = context
8181

8282
@cached_property
@@ -114,17 +114,40 @@ def resolve(self, root):
114114
# for field_name, field_resolver, field_args, context, info in self.partial_resolvers
115115
# }
116116

117+
def resolve_serially(self, root):
118+
def execute_field_callback(results, resolver):
119+
response_name, field_resolver, field_args, context, info = resolver
120+
121+
result = field_resolver(root, field_args, context, info)
122+
123+
if result is Undefined:
124+
return results
125+
126+
if is_promise(result):
127+
def collect_result(resolved_result):
128+
results[response_name] = resolved_result
129+
return results
130+
131+
return result.then(collect_result, None)
132+
133+
results[response_name] = result
134+
return results
135+
136+
def execute_field(prev_promise, resolver):
137+
return prev_promise.then(lambda results: execute_field_callback(results, resolver))
138+
139+
return functools.reduce(execute_field, self.partial_resolvers, Promise.resolve(OrderedDict()))
140+
117141
def __eq__(self, other):
118142
return isinstance(other, Fragment) and (
119143
other.type == self.type and
120144
other.selection_set == self.selection_set and
121-
other.context == self.context and
122-
other.execute_serially == self.execute_serially
145+
other.context == self.context
123146
)
124147

125148

126149
class AbstractFragment(object):
127-
def __init__(self, abstract_type, selection_set, context=None, info=None): # execute_serially=False
150+
def __init__(self, abstract_type, selection_set, context=None, info=None):
128151
self.abstract_type = abstract_type
129152
self.selection_set = selection_set
130153
self.context = context
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from ..executor import execute
2+
from graphql.language.parser import parse
3+
from graphql.type import (GraphQLArgument, GraphQLField, GraphQLInt,
4+
GraphQLList, GraphQLObjectType, GraphQLSchema,
5+
GraphQLString)
6+
7+
8+
class NumberHolder(object):
9+
10+
def __init__(self, n):
11+
self.theNumber = n
12+
13+
14+
class Root(object):
15+
16+
def __init__(self, n):
17+
self.numberHolder = NumberHolder(n)
18+
19+
def immediately_change_the_number(self, n):
20+
self.numberHolder.theNumber = n
21+
return self.numberHolder
22+
23+
def promise_to_change_the_number(self, n):
24+
# TODO: async
25+
return self.immediately_change_the_number(n)
26+
27+
def fail_to_change_the_number(self, n):
28+
raise Exception('Cannot change the number')
29+
30+
def promise_and_fail_to_change_the_number(self, n):
31+
# TODO: async
32+
self.fail_to_change_the_number(n)
33+
34+
35+
NumberHolderType = GraphQLObjectType('NumberHolder', {
36+
'theNumber': GraphQLField(GraphQLInt)
37+
})
38+
39+
QueryType = GraphQLObjectType('Query', {
40+
'numberHolder': GraphQLField(NumberHolderType)
41+
})
42+
43+
MutationType = GraphQLObjectType('Mutation', {
44+
'immediatelyChangeTheNumber': GraphQLField(
45+
NumberHolderType,
46+
args={'newNumber': GraphQLArgument(GraphQLInt)},
47+
resolver=lambda obj, args, *_:
48+
obj.immediately_change_the_number(args['newNumber'])),
49+
'promiseToChangeTheNumber': GraphQLField(
50+
NumberHolderType,
51+
args={'newNumber': GraphQLArgument(GraphQLInt)},
52+
resolver=lambda obj, args, *_:
53+
obj.promise_to_change_the_number(args['newNumber'])),
54+
'failToChangeTheNumber': GraphQLField(
55+
NumberHolderType,
56+
args={'newNumber': GraphQLArgument(GraphQLInt)},
57+
resolver=lambda obj, args, *_:
58+
obj.fail_to_change_the_number(args['newNumber'])),
59+
'promiseAndFailToChangeTheNumber': GraphQLField(
60+
NumberHolderType,
61+
args={'newNumber': GraphQLArgument(GraphQLInt)},
62+
resolver=lambda obj, args, *_:
63+
obj.promise_and_fail_to_change_the_number(args['newNumber'])),
64+
})
65+
66+
schema = GraphQLSchema(QueryType, MutationType)
67+
68+
69+
def assert_evaluate_mutations_serially(executor=None):
70+
doc = '''mutation M {
71+
first: immediatelyChangeTheNumber(newNumber: 1) {
72+
theNumber
73+
},
74+
second: promiseToChangeTheNumber(newNumber: 2) {
75+
theNumber
76+
},
77+
third: immediatelyChangeTheNumber(newNumber: 3) {
78+
theNumber
79+
}
80+
fourth: promiseToChangeTheNumber(newNumber: 4) {
81+
theNumber
82+
},
83+
fifth: immediatelyChangeTheNumber(newNumber: 5) {
84+
theNumber
85+
}
86+
}'''
87+
ast = parse(doc)
88+
result = execute(schema, ast, Root(6), operation_name='M', executor=executor)
89+
assert not result.errors
90+
assert result.data == \
91+
{
92+
'first': {'theNumber': 1},
93+
'second': {'theNumber': 2},
94+
'third': {'theNumber': 3},
95+
'fourth': {'theNumber': 4},
96+
'fifth': {'theNumber': 5},
97+
}
98+
99+
100+
def test_evaluates_mutations_serially():
101+
assert_evaluate_mutations_serially()
102+
103+
104+
def test_evaluates_mutations_correctly_in_the_presense_of_a_failed_mutation():
105+
doc = '''mutation M {
106+
first: immediatelyChangeTheNumber(newNumber: 1) {
107+
theNumber
108+
},
109+
second: promiseToChangeTheNumber(newNumber: 2) {
110+
theNumber
111+
},
112+
third: failToChangeTheNumber(newNumber: 3) {
113+
theNumber
114+
}
115+
fourth: promiseToChangeTheNumber(newNumber: 4) {
116+
theNumber
117+
},
118+
fifth: immediatelyChangeTheNumber(newNumber: 5) {
119+
theNumber
120+
}
121+
sixth: promiseAndFailToChangeTheNumber(newNumber: 6) {
122+
theNumber
123+
}
124+
}'''
125+
ast = parse(doc)
126+
result = execute(schema, ast, Root(6), operation_name='M')
127+
assert result.data == \
128+
{
129+
'first': {'theNumber': 1},
130+
'second': {'theNumber': 2},
131+
'third': None,
132+
'fourth': {'theNumber': 4},
133+
'fifth': {'theNumber': 5},
134+
'sixth': None,
135+
}
136+
assert len(result.errors) == 2
137+
# TODO: check error location
138+
assert result.errors[0].message == 'Cannot change the number'
139+
assert result.errors[1].message == 'Cannot change the number'

0 commit comments

Comments
 (0)