Skip to content

Commit 42a5ac3

Browse files
committed
Make experimental executor fully compatible with the spec
1 parent b356f66 commit 42a5ac3

File tree

3 files changed

+54
-21
lines changed

3 files changed

+54
-21
lines changed

graphql/execution/experimental/executor.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from promise import Promise
44

55
from ...type import GraphQLSchema
6-
from ..base import ExecutionContext, ExecutionResult, get_operation_root_type
6+
from ...pyutils.default_ordered_dict import DefaultOrderedDict
7+
from ..base import ExecutionContext, ExecutionResult, get_operation_root_type, collect_fields
78
from ..executors.sync import SyncExecutor
89
from ..middleware import MiddlewareManager
910
from .fragment import Fragment
@@ -60,7 +61,15 @@ def execute_operation(exe_context, operation, root_value):
6061
type = get_operation_root_type(exe_context.schema, operation)
6162
execute_serially = operation.operation == 'mutation'
6263

63-
fragment = Fragment(type=type, selection_set=operation.selection_set, context=exe_context)
64+
fields = collect_fields(
65+
exe_context,
66+
type,
67+
operation.selection_set,
68+
DefaultOrderedDict(list),
69+
set()
70+
)
71+
72+
fragment = Fragment(type=type, field_asts=fields, context=exe_context)
6473
if execute_serially:
6574
return fragment.resolve_serially(root_value)
6675
return fragment.resolve(root_value)

graphql/execution/experimental/fragment.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,24 @@ def get_base_type(type):
2222
return type
2323

2424

25-
def get_resolvers(context, type, selection_set):
26-
from .resolver import field_resolver
25+
def get_subfield_asts(context, return_type, field_asts):
2726
subfield_asts = DefaultOrderedDict(list)
2827
visited_fragment_names = set()
29-
if selection_set:
30-
subfield_asts = collect_fields(
31-
context, type, selection_set,
32-
subfield_asts, visited_fragment_names
33-
)
28+
for field_ast in field_asts:
29+
selection_set = field_ast.selection_set
30+
if selection_set:
31+
subfield_asts = collect_fields(
32+
context, return_type, selection_set,
33+
subfield_asts, visited_fragment_names
34+
)
35+
return subfield_asts
36+
37+
38+
def get_resolvers(context, type, field_asts):
39+
from .resolver import field_resolver
3440

3541
resolvers = []
36-
for response_name, field_asts in subfield_asts.items():
42+
for response_name, field_asts in field_asts.items():
3743
field_ast = field_asts[0]
3844
field_name = field_ast.name.value
3945
field_def = get_field_def(context and context.schema, type, field_name)
@@ -55,14 +61,14 @@ def get_resolvers(context, type, selection_set):
5561
if isinstance(field_base_type, GraphQLObjectType):
5662
field_fragment = Fragment(
5763
type=field_base_type,
58-
selection_set=field_ast.selection_set,
64+
field_asts=get_subfield_asts(context, field_base_type, field_asts),
5965
info=info,
6066
context=context
6167
)
6268
elif isinstance(field_base_type, (GraphQLInterfaceType, GraphQLUnionType)):
6369
field_fragment = AbstractFragment(
6470
abstract_type=field_base_type,
65-
selection_set=field_ast.selection_set,
71+
field_asts=field_asts,
6672
info=info,
6773
context=context
6874
)
@@ -78,9 +84,9 @@ def get_resolvers(context, type, selection_set):
7884

7985
class Fragment(object):
8086

81-
def __init__(self, type, selection_set, context=None, info=None):
87+
def __init__(self, type, field_asts, context=None, info=None):
8288
self.type = type
83-
self.selection_set = selection_set
89+
self.field_asts = field_asts
8490
self.context = context
8591
self.info = info
8692

@@ -89,7 +95,7 @@ def partial_resolvers(self):
8995
return get_resolvers(
9096
self.context,
9197
self.type,
92-
self.selection_set
98+
self.field_asts
9399
)
94100

95101
def have_type(self, root):
@@ -156,17 +162,17 @@ def execute_field(prev_promise, resolver):
156162
def __eq__(self, other):
157163
return isinstance(other, Fragment) and (
158164
other.type == self.type and
159-
other.selection_set == self.selection_set and
165+
other.field_asts == self.field_asts and
160166
other.context == self.context and
161167
other.info == self.info
162168
)
163169

164170

165171
class AbstractFragment(object):
166172

167-
def __init__(self, abstract_type, selection_set, context=None, info=None):
173+
def __init__(self, abstract_type, field_asts, context=None, info=None):
168174
self.abstract_type = abstract_type
169-
self.selection_set = selection_set
175+
self.field_asts = field_asts
170176
self.context = context
171177
self.info = info
172178
self._fragments = {}
@@ -183,7 +189,12 @@ def get_fragment(self, type):
183189
assert type in self.possible_types, (
184190
'Runtime Object type "{}" is not a possible type for "{}".'
185191
).format(type, self.abstract_type)
186-
self._fragments[type] = Fragment(type, self.selection_set, self.context, self.info)
192+
self._fragments[type] = Fragment(
193+
type,
194+
get_subfield_asts(self.context, type, self.field_asts),
195+
self.context,
196+
self.info
197+
)
187198
return self._fragments[type]
188199

189200
def resolve_type(self, result):

graphql/execution/experimental/resolver.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import collections
22
from functools import partial
33

4-
from promise import Promise
4+
from promise import Promise, is_thenable
55

66
from ...error import GraphQLError, GraphQLLocatedError
77
from ...type import (GraphQLEnumType, GraphQLInterfaceType, GraphQLList,
@@ -25,8 +25,18 @@ def is_promise(value):
2525
def on_complete_resolver(on_error, __func, exe_context, info, __resolver, *args, **kwargs):
2626
try:
2727
result = __resolver(*args, **kwargs)
28+
if isinstance(result, Exception):
29+
return on_error(result)
30+
# return Promise.resolve(result).then(__func).catch(on_error)
2831
if is_promise(result):
29-
return result.then(__func).catch(on_error)
32+
# TODO: Remove this, if a promise is resolved with an Exception,
33+
# it should raise by default. This is fixing an old behavior
34+
# in the Promise package
35+
def on_resolve(value):
36+
if isinstance(value, Exception):
37+
return on_error(value)
38+
return value
39+
return result.then(on_resolve).then(__func).catch(on_error)
3040
return __func(result)
3141
except Exception as e:
3242
return on_error(e)
@@ -76,6 +86,9 @@ def complete_object_value(fragment_resolve, exe_context, on_error, result):
7686
def field_resolver(field, fragment=None, exe_context=None, info=None):
7787
# resolver = exe_context.get_field_resolver(field.resolver or default_resolve_fn)
7888
resolver = field.resolver or default_resolve_fn
89+
if exe_context:
90+
# We decorate the resolver with the middleware
91+
resolver = exe_context.get_field_resolver(resolver)
7992
return type_resolver(field.type, resolver,
8093
fragment, exe_context, info, catch_error=True)
8194

0 commit comments

Comments
 (0)