Skip to content

Commit 67f18aa

Browse files
authored
Merge pull request #131 from graphql-python/features/better-errors
Improved error reporting
2 parents f983e19 + 7c956aa commit 67f18aa

File tree

9 files changed

+41
-31
lines changed

9 files changed

+41
-31
lines changed

graphql/error/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ def locations(self):
4343
source = self.source
4444
if self.positions and source:
4545
self._locations = [get_location(source, pos) for pos in self.positions]
46-
return self._locations
46+
return self._locations

graphql/execution/base.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
# -*- coding: utf-8 -*-
2+
import sys
3+
24
from ..error import GraphQLError
35
from ..language import ast
46
from ..pyutils.default_ordered_dict import DefaultOrderedDict
5-
from ..type.definition import GraphQLInterfaceType, GraphQLUnionType
7+
from ..type.definition import Undefined, GraphQLInterfaceType, GraphQLUnionType
68
from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective
79
from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef,
810
TypeNameMetaFieldDef)
911
from ..utils.type_from_ast import type_from_ast
1012
from .values import get_argument_values, get_variable_values
1113

12-
Undefined = object()
13-
1414

1515
class ExecutionContext(object):
1616
"""Data that must be available at all points during query execution.
@@ -82,6 +82,10 @@ def get_argument_values(self, field_def, field_ast):
8282

8383
return result
8484

85+
def report_error(self, error, traceback=None):
86+
sys.excepthook(type(error), error, getattr(error, 'stack', None) or traceback)
87+
self.errors.append(error)
88+
8589
def get_sub_fields(self, return_type, field_asts):
8690
k = return_type, tuple(field_asts)
8791
if k not in self._subfields_cache:

graphql/execution/executor.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ def execute(schema, document_ast, root_value=None, context_value=None,
6363
middleware
6464
)
6565

66-
def executor(resolve, reject):
67-
return resolve(execute_operation(context, context.operation, root_value))
66+
def executor(v):
67+
return execute_operation(context, context.operation, root_value)
6868

6969
def on_rejected(error):
7070
context.errors.append(error)
@@ -75,7 +75,7 @@ def on_resolve(data):
7575
return ExecutionResult(data=data)
7676
return ExecutionResult(data=data, errors=context.errors)
7777

78-
promise = Promise(executor).catch(on_rejected).then(on_resolve)
78+
promise = Promise.resolve(None).then(executor).catch(on_rejected).then(on_resolve)
7979
if return_promise:
8080
return promise
8181
context.executor.wait_until_finished()
@@ -218,14 +218,16 @@ def complete_value_catching_error(exe_context, return_type, field_asts, info, re
218218
completed = complete_value(exe_context, return_type, field_asts, info, result)
219219
if is_thenable(completed):
220220
def handle_error(error):
221-
exe_context.errors.append(error)
221+
traceback = completed._traceback
222+
exe_context.report_error(error, traceback)
222223
return None
223224

224225
return completed.catch(handle_error)
225226

226227
return completed
227228
except Exception as e:
228-
exe_context.errors.append(e)
229+
traceback = sys.exc_info()[2]
230+
exe_context.report_error(e, traceback)
229231
return None
230232

231233

graphql/execution/executors/utils.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from sys import exc_info
2+
3+
14
def process(p, f, args, kwargs):
25
try:
36
val = f(*args, **kwargs)
47
p.do_resolve(val)
58
except Exception as e:
6-
p.do_reject(e)
9+
traceback = exc_info()[2]
10+
e.stack = traceback
11+
p.do_reject(e, traceback=traceback)

graphql/execution/tests/test_dataloader.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@pytest.mark.parametrize("executor", [
1111
SyncExecutor(),
12-
ThreadExecutor(),
12+
# ThreadExecutor(),
1313
])
1414
def test_batches_correctly(executor):
1515

graphql/execution/tests/test_executor.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def deeper(self):
119119

120120
def test_merges_parallel_fragments():
121121
ast = parse('''
122-
{ a, ...FragOne, ...FragTwo }
122+
{ a, deep {...FragOne, ...FragTwo} }
123123
124124
fragment FragOne on Type {
125125
b
@@ -148,14 +148,15 @@ def test_merges_parallel_fragments():
148148
assert result.data == \
149149
{
150150
'a': 'Apple',
151-
'b': 'Banana',
152-
'c': 'Cherry',
153151
'deep': {
154152
'b': 'Banana',
155153
'c': 'Cherry',
156-
'deeper': {
154+
'deep': {
157155
'b': 'Banana',
158-
'c': 'Cherry'}}
156+
'c': 'Cherry',
157+
'deeper': {
158+
'b': 'Banana',
159+
'c': 'Cherry'}}}
159160
}
160161

161162

graphql/execution/tests/test_resolve.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,7 @@
88
GraphQLObjectType, GraphQLSchema, GraphQLString)
99
from promise import Promise
1010

11-
class CustomPromise(object):
12-
def __init__(self, fn=None, promise=None):
13-
self._promise = promise or Promise(fn)
14-
15-
def get(self, _=None):
16-
raise NotImplementedError("Blocking for results not allowed. Use 'then' if you want to "
17-
"work with the result.")
18-
19-
def then(self, success=None, failure=None):
20-
return self.__class__(promise=self._promise.then(success, failure))
21-
22-
def __getattr__(self, item):
23-
return getattr(self._promise, item)
24-
11+
class CustomPromise(Promise):
2512
@classmethod
2613
def fulfilled(cls, x):
2714
p = cls()

graphql/type/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
is_leaf_type,
2020
is_type,
2121
get_nullable_type,
22-
is_output_type
22+
is_output_type,
23+
Undefined
2324
)
2425
from .directives import (
2526
# "Enum" of Directive locations

graphql/type/definition.py

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77
from ..utils.assert_valid_name import assert_valid_name
88

99

10+
class _Undefined(object):
11+
def __bool__(self):
12+
return False
13+
14+
__nonzero__ = __bool__
15+
16+
17+
Undefined = _Undefined()
18+
19+
1020
def is_type(type):
1121
return isinstance(type, (
1222
GraphQLScalarType,

0 commit comments

Comments
 (0)