Skip to content

Commit 81bcf8c

Browse files
committed
Improved experimental execution speed with simulated Ordered dicts
1 parent 197d3ee commit 81bcf8c

File tree

5 files changed

+69
-26
lines changed

5 files changed

+69
-26
lines changed

graphql/error/tests/test_base.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,14 @@ def resolver(context, *_):
4747
('resolve_or_error', 'return executor.execute(resolve_fn, source, args, context, info)'),
4848
('execute', 'return fn(*args, **kwargs)'), ('resolver', "raise Exception('Failed')")
4949
]
50+
# assert formatted_tb == [
51+
# ('test_reraise', 'result.errors[0].reraise()'),
52+
# ('reraise', 'six.reraise(type(self), self, self.stack)'),
53+
# ('on_complete_resolver', 'result = __resolver(*args, **kwargs)'),
54+
# # ('reraise', 'raise value.with_traceback(tb)'),
55+
# # ('resolve_or_error', 'return executor.execute(resolve_fn, source, args, context, info)'),
56+
# # ('execute', 'return fn(*args, **kwargs)'),
57+
# ('resolver', "raise Exception('Failed')")
58+
# ]
59+
5060
assert str(exc_info.value) == 'Failed'

graphql/execution/executors/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
def process(p, f, args, kwargs):
22
try:
33
val = f(*args, **kwargs)
4-
p.fulfill(val)
4+
p.do_resolve(val)
55
except Exception as e:
6-
p.reject(e)
6+
p.do_reject(e)

graphql/execution/experimental/fragment.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools
22

3-
from promise import Promise, promise_for_dict
3+
from promise import Promise, is_thenable, promise_for_dict
44

55
from ...pyutils.cached_property import cached_property
66
from ...pyutils.default_ordered_dict import DefaultOrderedDict
@@ -10,10 +10,7 @@
1010
from ..base import ResolveInfo, Undefined, collect_fields, get_field_def
1111
from ..values import get_argument_values
1212
from ...error import GraphQLError
13-
14-
15-
def is_promise(obj):
16-
return isinstance(obj, Promise)
13+
from .utils import imap, normal_map
1714

1815

1916
def get_base_type(type):
@@ -78,7 +75,20 @@ def get_resolvers(context, type, field_asts):
7875
field_ast.arguments,
7976
context and context.variable_values
8077
)
81-
yield (response_name, resolver, args, context and context.context_value, info)
78+
yield (response_name, Field(resolver, args, context and context.context_value, info))
79+
80+
81+
class Field(object):
82+
__slots__ = ('fn', 'args', 'context', 'info')
83+
84+
def __init__(self, fn, args, context, info):
85+
self.fn = fn
86+
self.args = args
87+
self.context = context
88+
self.info = info
89+
90+
def execute(self, root):
91+
return self.fn(root, self.args, self.context, self.info)
8292

8393

8494
class Fragment(object):
@@ -97,6 +107,22 @@ def partial_resolvers(self):
97107
self.field_asts
98108
))
99109

110+
@cached_property
111+
def fragment_container(self):
112+
fields = zip(*self.partial_resolvers)[0]
113+
class FragmentInstance(dict):
114+
# def __init__(self):
115+
# self.fields = fields
116+
# _fields = ('c','b','a')
117+
set = dict.__setitem__
118+
# def set(self, name, value):
119+
# self[name] = value
120+
121+
def __iter__(self):
122+
return iter(fields)
123+
124+
return FragmentInstance
125+
100126
def have_type(self, root):
101127
return not self.type.is_type_of or self.type.is_type_of(root, self.context.context_value, self.info)
102128

@@ -109,18 +135,18 @@ def resolve(self, root):
109135

110136
contains_promise = False
111137

112-
final_results = OrderedDict()
138+
final_results = self.fragment_container()
113139
# return OrderedDict(
114140
# ((field_name, field_resolver(root, field_args, context, info))
115141
# for field_name, field_resolver, field_args, context, info in self.partial_resolvers)
116142
# )
117-
for response_name, field_resolver, field_args, context, info in self.partial_resolvers:
143+
for response_name, field_resolver in self.partial_resolvers:
118144

119-
result = field_resolver(root, field_args, context, info)
145+
result = field_resolver.execute(root)
120146
if result is Undefined:
121147
continue
122148

123-
if not contains_promise and is_promise(result):
149+
if not contains_promise and is_thenable(result):
124150
contains_promise = True
125151

126152
final_results[response_name] = result
@@ -136,14 +162,14 @@ def resolve(self, root):
136162

137163
def resolve_serially(self, root):
138164
def execute_field_callback(results, resolver):
139-
response_name, field_resolver, field_args, context, info = resolver
165+
response_name, field_resolver = resolver
140166

141-
result = field_resolver(root, field_args, context, info)
167+
result = field_resolver.execute(root)
142168

143169
if result is Undefined:
144170
return results
145171

146-
if is_promise(result):
172+
if is_thenable(result):
147173
def collect_result(resolved_result):
148174
results[response_name] = resolved_result
149175
return results
@@ -156,7 +182,7 @@ def collect_result(resolved_result):
156182
def execute_field(prev_promise, resolver):
157183
return prev_promise.then(lambda results: execute_field_callback(results, resolver))
158184

159-
return functools.reduce(execute_field, self.partial_resolvers, Promise.resolve(OrderedDict()))
185+
return functools.reduce(execute_field, self.partial_resolvers, Promise.resolve(self.fragment_container()))
160186

161187
def __eq__(self, other):
162188
return isinstance(other, Fragment) and (
@@ -180,6 +206,12 @@ def __init__(self, abstract_type, field_asts, context=None, info=None):
180206
def possible_types(self):
181207
return self.context.schema.get_possible_types(self.abstract_type)
182208

209+
@cached_property
210+
def possible_types_with_is_type_of(self):
211+
return [
212+
(type, type.is_type_of) for type in self.possible_types if callable(type.is_type_of)
213+
]
214+
183215
def get_fragment(self, type):
184216
if isinstance(type, str):
185217
type = self.context.schema.get_type(type)
@@ -194,6 +226,7 @@ def get_fragment(self, type):
194226
self.context,
195227
self.info
196228
)
229+
197230
return self._fragments[type]
198231

199232
def resolve_type(self, result):
@@ -202,10 +235,10 @@ def resolve_type(self, result):
202235

203236
if return_type.resolve_type:
204237
return return_type.resolve_type(result, context, self.info)
205-
else:
206-
for type in self.possible_types:
207-
if callable(type.is_type_of) and type.is_type_of(result, context, self.info):
208-
return type
238+
239+
for type, is_type_of in self.possible_types_with_is_type_of:
240+
if is_type_of(result, context, self.info):
241+
return type
209242

210243
def resolve(self, root):
211244
_type = self.resolve_type(root)

graphql/execution/experimental/resolver.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
from ...execution import executor
1313
from .utils import imap, normal_map
1414

15-
def is_promise(value):
16-
return isinstance(value, Promise)
17-
1815

1916
def on_complete_resolver(on_error, __func, exe_context, info, __resolver, *args, **kwargs):
2017
try:
@@ -46,7 +43,7 @@ def complete_list_value(inner_resolver, exe_context, info, on_error, result):
4643

4744
completed_results = normal_map(inner_resolver, result)
4845

49-
if not any(imap(is_promise, completed_results)):
46+
if not any(imap(is_thenable, completed_results)):
5047
return completed_results
5148

5249
return Promise.all(completed_results).catch(on_error)
@@ -72,7 +69,7 @@ def complete_object_value(fragment_resolve, exe_context, on_error, result):
7269
return None
7370

7471
result = fragment_resolve(result)
75-
if is_promise(result):
72+
if is_thenable(result):
7673
return result.catch(on_error)
7774
return result
7875

graphql/pyutils/contain_subset.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
def contain_subset(expected, actual):
55
t_actual = type(actual)
66
t_expected = type(expected)
7-
if not(issubclass(t_actual, t_expected) or issubclass(t_expected, t_actual)):
7+
actual_is_dict = issubclass(t_actual, dict)
8+
expected_is_dict = issubclass(t_expected, dict)
9+
both_dicts = actual_is_dict and expected_is_dict
10+
if not(both_dicts) and not(issubclass(t_actual, t_expected) or issubclass(t_expected, t_actual)):
811
return False
912
if not isinstance(expected, obj) or expected is None:
1013
return expected == actual

0 commit comments

Comments
 (0)