Skip to content

Commit e0bb443

Browse files
committed
Add traceback to GraphQLErrors
Should solve issue #23 Also replicates graphql/graphql-js@f0ae3f4
1 parent f284e88 commit e0bb443

File tree

3 files changed

+91
-52
lines changed

3 files changed

+91
-52
lines changed

graphql/error/graphql_error.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from sys import exc_info
12
from typing import Any, Dict, List, Optional, Sequence, Union, TYPE_CHECKING
23

34
from .format_error import format_error
@@ -118,12 +119,21 @@ def __init__(
118119
path = list(path)
119120
self.path = path or None # type: ignore
120121
self.original_error = original_error
121-
if not extensions and original_error:
122-
try:
123-
extensions = original_error.extensions # type: ignore
124-
except AttributeError:
125-
pass
122+
if original_error:
123+
if not self.__traceback__:
124+
self.__traceback__ = original_error.__traceback__
125+
if original_error.__cause__:
126+
self.__cause__ = original_error.__cause__
127+
elif original_error.__context__:
128+
self.__context__ = original_error.__context__
129+
if not extensions:
130+
try:
131+
extensions = original_error.extensions # type: ignore
132+
except AttributeError:
133+
pass
126134
self.extensions = extensions or {}
135+
if not self.__traceback__:
136+
self.__traceback__ = exc_info()[2]
127137

128138
def __str__(self):
129139
return print_error(self)

tests/error/test_graphql_error.py

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,123 @@
1+
from typing import cast
2+
13
from graphql.error import GraphQLError, format_error
2-
from graphql.language import parse, Source
4+
from graphql.language import parse, OperationDefinitionNode, Source
5+
from graphql.pyutils import dedent
6+
7+
8+
source = Source(
9+
dedent(
10+
"""
11+
{
12+
field
13+
}
14+
"""
15+
)
16+
)
17+
18+
ast = parse(source)
19+
operation_node = ast.definitions[0]
20+
operation_node = cast(OperationDefinitionNode, operation_node)
21+
assert operation_node and operation_node.kind == "operation_definition"
22+
field_node = operation_node.selection_set.selections[0]
23+
assert field_node
324

425

526
def describe_graphql_error():
627
def is_a_class_and_is_a_subclass_of_exception():
28+
assert type(GraphQLError) is type
729
assert issubclass(GraphQLError, Exception)
8-
assert isinstance(GraphQLError("msg"), GraphQLError)
30+
assert isinstance(GraphQLError("str"), Exception)
31+
assert isinstance(GraphQLError("str"), GraphQLError)
932

1033
def has_a_name_message_and_stack_trace():
1134
e = GraphQLError("msg")
1235
assert e.__class__.__name__ == "GraphQLError"
1336
assert e.message == "msg"
1437

15-
def stores_the_original_error():
16-
original = Exception("original")
38+
def uses_the_stack_of_an_original_error():
39+
try:
40+
raise RuntimeError("original")
41+
except RuntimeError as e:
42+
original = e
1743
e = GraphQLError("msg", original_error=original)
1844
assert e.__class__.__name__ == "GraphQLError"
45+
assert e.__traceback__ is original.__traceback__
1946
assert e.message == "msg"
20-
assert e.original_error == original
47+
assert e.original_error is original
48+
assert str(e.original_error) == "original"
49+
50+
def creates_new_stack_if_original_error_has_no_stack():
51+
try:
52+
raise RuntimeError
53+
except RuntimeError as original_with_traceback:
54+
original_traceback = original_with_traceback.__traceback__
55+
original = RuntimeError("original")
56+
e = GraphQLError("msg", original_error=original)
57+
assert e.__class__.__name__ == "GraphQLError"
58+
assert original.__traceback__ is None
59+
assert original_traceback is not None
60+
assert e.__traceback__ is original_traceback
61+
assert e.message == "msg"
62+
assert e.original_error is original
63+
assert str(e.original_error) == "original"
2164

2265
def converts_nodes_to_positions_and_locations():
23-
source = Source("{\n field\n}")
24-
ast = parse(source)
25-
# noinspection PyUnresolvedReferences
26-
field_node = ast.definitions[0].selection_set.selections[0]
2766
e = GraphQLError("msg", [field_node])
2867
assert e.nodes == [field_node]
2968
assert e.source is source
30-
assert e.positions == [8]
31-
assert e.locations == [(2, 7)]
69+
assert e.positions == [4]
70+
assert e.locations == [(2, 3)]
3271

3372
def converts_single_node_to_positions_and_locations():
34-
source = Source("{\n field\n}")
35-
ast = parse(source)
36-
# noinspection PyUnresolvedReferences
37-
field_node = ast.definitions[0].selection_set.selections[0]
3873
e = GraphQLError("msg", field_node) # Non-array value.
3974
assert e.nodes == [field_node]
4075
assert e.source is source
41-
assert e.positions == [8]
42-
assert e.locations == [(2, 7)]
76+
assert e.positions == [4]
77+
assert e.locations == [(2, 3)]
4378

4479
def converts_node_with_loc_start_zero_to_positions_and_locations():
45-
source = Source("{\n field\n}")
46-
ast = parse(source)
47-
operations_node = ast.definitions[0]
48-
e = GraphQLError("msg", [operations_node])
49-
assert e.nodes == [operations_node]
80+
e = GraphQLError("msg", [operation_node])
81+
assert e.nodes == [operation_node]
5082
assert e.source is source
5183
assert e.positions == [0]
5284
assert e.locations == [(1, 1)]
5385

5486
def converts_source_and_positions_to_locations():
55-
source = Source("{\n field\n}")
56-
# noinspection PyArgumentEqualDefault
57-
e = GraphQLError("msg", None, source, [10])
87+
e = GraphQLError("msg", None, source, [6])
5888
assert e.nodes is None
5989
assert e.source is source
60-
assert e.positions == [10]
61-
assert e.locations == [(2, 9)]
90+
assert e.positions == [6]
91+
assert e.locations == [(2, 5)]
6292

6393
def serializes_to_include_message():
6494
e = GraphQLError("msg")
6595
assert str(e) == "msg"
6696
assert repr(e) == "GraphQLError('msg')"
6797

6898
def serializes_to_include_message_and_locations():
69-
# noinspection PyUnresolvedReferences
70-
node = parse("{ field }").definitions[0].selection_set.selections[0]
71-
e = GraphQLError("msg", [node])
99+
e = GraphQLError("msg", [field_node])
72100
assert "msg" in str(e)
73-
assert "(1:3)" in str(e)
101+
assert "(2:3)" in str(e)
74102
assert repr(e) == (
75-
"GraphQLError('msg', locations=[SourceLocation(line=1, column=3)])"
103+
"GraphQLError('msg', locations=[SourceLocation(line=2, column=3)])"
76104
)
77105

78106
def serializes_to_include_path():
79107
path = ["path", 3, "to", "field"]
80-
# noinspection PyArgumentEqualDefault
81-
e = GraphQLError("msg", None, None, None, path)
108+
e = GraphQLError("msg", path=path)
82109
assert e.path is path
83110
assert repr(e) == "GraphQLError('msg', path=['path', 3, 'to', 'field'])"
84111

85112
def default_error_formatter_includes_path():
86113
path = ["path", 3, "to", "field"]
87-
# noinspection PyArgumentEqualDefault
88-
e = GraphQLError("msg", None, None, None, path)
114+
e = GraphQLError("msg", path=path)
89115
formatted = format_error(e)
90116
assert formatted == e.formatted
91117
assert formatted == {"message": "msg", "locations": None, "path": path}
92118

93119
def default_error_formatter_includes_extension_fields():
94-
# noinspection PyArgumentEqualDefault
95-
e = GraphQLError("msg", None, None, None, None, None, {"foo": "bar"})
120+
e = GraphQLError("msg", extensions={"foo": "bar"})
96121
formatted = format_error(e)
97122
assert formatted == e.formatted
98123
assert formatted == {

tests/error/test_print_error.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def prints_line_numbers_with_correct_padding():
4444
)
4545

4646
def prints_an_error_with_nodes_from_different_sources():
47-
source_a = parse(
47+
doc_a = parse(
4848
Source(
4949
dedent(
5050
"""
@@ -56,10 +56,11 @@ def prints_an_error_with_nodes_from_different_sources():
5656
"SourceA",
5757
)
5858
)
59-
field_type_a = (
60-
cast(ObjectTypeDefinitionNode, source_a.definitions[0]).fields[0].type
61-
)
62-
source_b = parse(
59+
op_a = doc_a.definitions[0]
60+
op_a = cast(ObjectTypeDefinitionNode, op_a)
61+
assert op_a and op_a.kind == "object_type_definition" and op_a.fields
62+
field_a = op_a.fields[0]
63+
doc_b = parse(
6364
Source(
6465
dedent(
6566
"""
@@ -71,12 +72,15 @@ def prints_an_error_with_nodes_from_different_sources():
7172
"SourceB",
7273
)
7374
)
74-
field_type_b = (
75-
cast(ObjectTypeDefinitionNode, source_b.definitions[0]).fields[0].type
76-
)
75+
op_b = doc_b.definitions[0]
76+
op_b = cast(ObjectTypeDefinitionNode, op_b)
77+
assert op_b and op_b.kind == "object_type_definition" and op_b.fields
78+
field_b = op_b.fields[0]
79+
assert field_a and field_b
7780
error = GraphQLError(
78-
"Example error with two nodes", [field_type_a, field_type_b]
81+
"Example error with two nodes", [field_a.type, field_b.type]
7982
)
83+
8084
printed_error = print_error(error)
8185
assert printed_error == dedent(
8286
"""

0 commit comments

Comments
 (0)