Skip to content

Commit 3359fe6

Browse files
committed
Add an extension field to the ExecutionResult (#107)
Also make ExecutionResult a class instead of a NamedTuple, but do it as backward compatible as possible. Replicates graphql/graphql-js@fb042fc
1 parent 3093b5b commit 3359fe6

File tree

2 files changed

+156
-4
lines changed

2 files changed

+156
-4
lines changed

src/graphql/execution/execute.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
Dict,
88
Iterable,
99
List,
10-
NamedTuple,
1110
Optional,
1211
Set,
1312
Union,
@@ -95,19 +94,66 @@
9594
# 3) inline fragment "spreads" e.g. "...on Type { a }"
9695

9796

98-
class ExecutionResult(NamedTuple):
97+
class ExecutionResult:
9998
"""The result of GraphQL execution.
10099
101100
- ``data`` is the result of a successful execution of the query.
102101
- ``errors`` is included when any errors occurred as a non-empty list.
102+
- ``extensions`` is reserved for adding non-standard properties.
103103
"""
104104

105+
__slots__ = "data", "errors", "extensions"
106+
105107
data: Optional[Dict[str, Any]]
106108
errors: Optional[List[GraphQLError]]
109+
extensions: Optional[Dict[str, Any]]
110+
111+
def __init__(
112+
self,
113+
data: Optional[Dict[str, Any]] = None,
114+
errors: Optional[List[GraphQLError]] = None,
115+
extensions: Optional[Dict[str, Any]] = None,
116+
):
117+
self.data = data
118+
self.errors = errors
119+
self.extensions = extensions
120+
121+
def __repr__(self) -> str:
122+
name = self.__class__.__name__
123+
ext = "" if self.extensions is None else f", extensions={self.extensions}"
124+
return f"{name}(data={self.data!r}, errors={self.errors!r}{ext})"
125+
126+
def __iter__(self) -> Iterable[Any]:
127+
return iter((self.data, self.errors))
128+
129+
@property
130+
def formatted(self) -> Dict[str, Any]:
131+
"""Get execution result formatted according to the specification."""
132+
if self.extensions is None:
133+
return dict(data=self.data, errors=self.errors)
134+
return dict(data=self.data, errors=self.errors, extensions=self.extensions)
135+
136+
def __eq__(self, other: Any) -> bool:
137+
if isinstance(other, dict):
138+
if "extensions" not in other:
139+
return other == dict(data=self.data, errors=self.errors)
140+
return other == dict(
141+
data=self.data, errors=self.errors, extensions=self.extensions
142+
)
143+
if isinstance(other, tuple):
144+
if len(other) == 2:
145+
return other == (self.data, self.errors)
146+
return other == (self.data, self.errors, self.extensions)
147+
return (
148+
isinstance(other, self.__class__)
149+
and other.data == self.data
150+
and other.errors == self.errors
151+
and other.extensions == self.extensions
152+
)
107153

154+
def __ne__(self, other: Any) -> bool:
155+
return not self == other
108156

109-
# noinspection PyTypeHints
110-
ExecutionResult.__new__.__defaults__ = (None, None) # type: ignore
111157

112158
Middleware = Optional[Union[Tuple, List, MiddlewareManager]]
113159

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from pytest import raises # type: ignore
2+
3+
from graphql.error import GraphQLError
4+
from graphql.execution import ExecutionResult
5+
6+
7+
def describe_execution_result():
8+
9+
data = {"foo": "Some data"}
10+
error = GraphQLError("Some error")
11+
errors = [error]
12+
extensions = {"bar": "Some extension"}
13+
14+
def initializes_properly():
15+
res = ExecutionResult(data, errors)
16+
assert res.data is data
17+
assert res.errors is errors
18+
assert res.extensions is None
19+
res = ExecutionResult(data, errors, extensions)
20+
assert res.data is data
21+
assert res.errors is errors
22+
assert res.extensions is extensions
23+
24+
def prints_a_representation():
25+
assert repr(ExecutionResult(data, errors)) == (
26+
"ExecutionResult(data={'foo': 'Some data'},"
27+
" errors=[GraphQLError('Some error')])"
28+
)
29+
assert repr(ExecutionResult(data, errors, extensions)) == (
30+
"ExecutionResult(data={'foo': 'Some data'},"
31+
" errors=[GraphQLError('Some error')],"
32+
" extensions={'bar': 'Some extension'})"
33+
)
34+
35+
def formats_properly():
36+
res = ExecutionResult(data, errors)
37+
assert res.formatted == {"data": data, "errors": errors}
38+
res = ExecutionResult(data, errors, extensions)
39+
assert res.formatted == {
40+
"data": data,
41+
"errors": errors,
42+
"extensions": extensions,
43+
}
44+
45+
def compares_to_dict():
46+
res = ExecutionResult(data, errors)
47+
assert res == {"data": data, "errors": errors}
48+
assert res == {"data": data, "errors": errors, "extensions": None}
49+
assert res != {"data": data, "errors": None}
50+
assert res != {"data": None, "errors": errors}
51+
assert res != {"data": data, "errors": errors, "extensions": extensions}
52+
res = ExecutionResult(data, errors, extensions)
53+
assert res == {"data": data, "errors": errors}
54+
assert res == {"data": data, "errors": errors, "extensions": extensions}
55+
assert res != {"data": data, "errors": None}
56+
assert res != {"data": None, "errors": errors}
57+
assert res != {"data": data, "errors": errors, "extensions": None}
58+
59+
def compares_to_tuple():
60+
res = ExecutionResult(data, errors)
61+
assert res == (data, errors)
62+
assert res == (data, errors, None)
63+
assert res != (data, None)
64+
assert res != (None, errors)
65+
assert res != (data, errors, extensions)
66+
res = ExecutionResult(data, errors, extensions)
67+
assert res == (data, errors)
68+
assert res == (data, errors, extensions)
69+
assert res != (data, None)
70+
assert res != (None, errors)
71+
assert res != (data, errors, None)
72+
73+
def does_not_compare_to_list():
74+
res = ExecutionResult(data, errors)
75+
assert res != [data, errors]
76+
res = ExecutionResult(data, errors, extensions)
77+
assert res != [data, errors, extensions]
78+
79+
def compares_to_another_execution_result():
80+
res1 = ExecutionResult(data, errors)
81+
res2 = ExecutionResult(data, errors)
82+
assert res1 == res2
83+
res2 = ExecutionResult({"foo": "other data"}, errors)
84+
assert res1 != res2
85+
res2 = ExecutionResult(data, [GraphQLError("Another error")])
86+
assert res1 != res2
87+
res2 = ExecutionResult(data, errors, extensions)
88+
assert res1 != res2
89+
res1 = ExecutionResult(data, errors, extensions)
90+
res2 = ExecutionResult(data, errors, extensions)
91+
assert res1 == res2
92+
res2 = ExecutionResult({"foo": "other data"}, errors, extensions)
93+
assert res1 != res2
94+
res2 = ExecutionResult(data, [GraphQLError("Another error")], extensions)
95+
assert res1 != res2
96+
res2 = ExecutionResult(data, errors, {"bar": "Another extension"})
97+
assert res1 != res2
98+
99+
def unpacks_as_two_tuple():
100+
res = ExecutionResult(data, errors)
101+
res_data, res_errors = res # type: ignore
102+
assert res_data == data # type: ignore
103+
assert res_errors == errors # type: ignore
104+
with raises(ValueError):
105+
res = ExecutionResult(data, errors, extensions)
106+
_res_data, _res_errors, _res_extensions = res # type: ignore

0 commit comments

Comments
 (0)