Skip to content

Commit 8f736b7

Browse files
authored
Add traceback to errors from failed promises (#240)
The stack attribute of GraphQLLocatedErrors was not set for errors that resulted from asynchronous resolvers. We add a unit test for this problem and a solution that works with Python >= 3. In Python 2 this problem is not yet solved, and may be never solved, because dealing with stack traces is much more difficult there.
1 parent 0cddba7 commit 8f736b7

File tree

2 files changed

+50
-16
lines changed

2 files changed

+50
-16
lines changed

graphql/error/located_error.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ def __init__(
2626
else:
2727
message = "An unknown error occurred."
2828

29-
stack = original_error and getattr(original_error, "stack", None)
30-
if not stack:
31-
stack = sys.exc_info()[2]
29+
stack = (
30+
original_error
31+
and (
32+
getattr(original_error, "stack", None)
33+
# unfortunately, this is only available in Python 3:
34+
or getattr(original_error, "__traceback__", None)
35+
)
36+
or sys.exc_info()[2]
37+
)
3238

3339
extensions = (
3440
getattr(original_error, "extensions", None) if original_error else None

graphql/error/tests/test_base.py

+41-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
import sys
2+
13
import pytest
24
import traceback
35

6+
from promise import Promise
7+
48
from graphql.execution import execute
59
from graphql.language.parser import parse
610
from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString
@@ -46,28 +50,52 @@ def resolver(context, *_):
4650

4751
extracted = traceback.extract_tb(exc_info.tb)
4852
formatted_tb = [row[2:] for row in extracted]
49-
if formatted_tb[2][0] == "reraise":
50-
formatted_tb[2:] = formatted_tb[3:]
53+
formatted_tb = [tb for tb in formatted_tb if tb[0] != "reraise"]
5154

5255
assert formatted_tb == [
5356
("test_reraise", "result.errors[0].reraise()"),
54-
("reraise", "six.reraise(type(self), self, self.stack)"),
55-
# ('reraise', 'raise value.with_traceback(tb)'),
5657
(
5758
"resolve_or_error",
5859
"return executor.execute(resolve_fn, source, info, **args)",
5960
),
6061
("execute", "return fn(*args, **kwargs)"),
6162
("resolver", 'raise Exception("Failed")'),
6263
]
63-
# assert formatted_tb == [
64-
# ('test_reraise', 'result.errors[0].reraise()'),
65-
# ('reraise', 'six.reraise(type(self), self, self.stack)'),
66-
# ('on_complete_resolver', 'result = __resolver(*args, **kwargs)'),
67-
# # ('reraise', 'raise value.with_traceback(tb)'),
68-
# # ('resolve_or_error', 'return executor.execute(resolve_fn, source, info, **args)'),
69-
# # ('execute', 'return fn(*args, **kwargs)'),
70-
# ('resolver', "raise Exception('Failed')")
71-
# ]
64+
65+
assert str(exc_info.value) == "Failed"
66+
67+
68+
@pytest.mark.skipif(sys.version_info < (3,), reason="this works only with Python 3")
69+
def test_reraise_from_promise():
70+
# type: () -> None
71+
ast = parse("query Example { a }")
72+
73+
def fail():
74+
raise Exception("Failed")
75+
76+
def resolver(context, *_):
77+
# type: (Optional[Any], *ResolveInfo) -> None
78+
return Promise(lambda resolve, reject: resolve(fail()))
79+
80+
Type = GraphQLObjectType(
81+
"Type", {"a": GraphQLField(GraphQLString, resolver=resolver)}
82+
)
83+
84+
result = execute(GraphQLSchema(Type), ast)
85+
with pytest.raises(Exception) as exc_info:
86+
result.errors[0].reraise()
87+
88+
extracted = traceback.extract_tb(exc_info.tb)
89+
formatted_tb = [row[2:] for row in extracted]
90+
formatted_tb = [tb for tb in formatted_tb if tb[0] != "reraise"]
91+
92+
print(formatted_tb)
93+
94+
assert formatted_tb == [
95+
("test_reraise_from_promise", "result.errors[0].reraise()"),
96+
("_resolve_from_executor", "executor(resolve, reject)"),
97+
("<lambda>", "return Promise(lambda resolve, reject: resolve(fail()))"),
98+
("fail", 'raise Exception("Failed")'),
99+
]
72100

73101
assert str(exc_info.value) == "Failed"

0 commit comments

Comments
 (0)