Skip to content

Commit 09ff14f

Browse files
committed
Deprecate format_error
Also use TypedDict for formatted results, and do not include unused keys any more. Replicates graphql/graphql-js@8423d33
1 parent 8a88ab2 commit 09ff14f

17 files changed

+205
-122
lines changed

docs/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@
9696

9797
autodoc_type_aliases = {
9898
'AwaitableOrValue': 'graphql.pyutils.AwaitableOrValue',
99+
'FormattedSourceLocation': 'graphql.language.FormattedSourceLocation',
100+
'Middleware': 'graphql.execution.Middleware',
99101
'TypeMap': 'graphql.schema.TypeMap'
100102
}
101103

@@ -131,6 +133,7 @@
131133
enum.Enum
132134
traceback
133135
types.TracebackType
136+
FormattedSourceLocation
134137
asyncio.events.AbstractEventLoop
135138
graphql.language.lexer.EscapeSequence
136139
graphql.subscription.map_async_iterator.MapAsyncIterator

docs/modules/error.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,15 @@ Error
44
.. currentmodule:: graphql.error
55

66
.. automodule:: graphql.error
7+
:no-members:
8+
:no-inherited-members:
9+
10+
.. autoclass:: GraphQLError
11+
12+
.. autoclass:: GraphQLSyntaxError
13+
14+
.. autoclass:: GraphQLFormattedError
15+
:no-inherited-members:
16+
:no-special-members:
17+
18+
.. autofunction:: located_error

docs/modules/execution.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,26 @@ Execution
44
.. currentmodule:: graphql.execution
55

66
.. automodule:: graphql.execution
7+
:no-members:
8+
:no-inherited-members:
9+
10+
.. autofunction:: execute
11+
12+
.. autofunction:: execute_sync
13+
14+
.. autofunction:: default_field_resolver
15+
16+
.. autofunction:: default_type_resolver
17+
18+
.. autoclass:: ExecutionContext
19+
20+
.. autoclass:: ExecutionResult
21+
22+
.. autoclass:: FormattedExecutionResult
23+
:no-inherited-members:
24+
25+
.. autoclass:: Middleware
26+
27+
.. autoclass:: MiddlewareManager
28+
29+
.. autofunction:: get_directive_values

docs/modules/language.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ Location
105105
.. autofunction:: get_location
106106
.. autoclass:: SourceLocation
107107
.. autofunction:: print_location
108+
.. autoclass:: FormattedSourceLocation
109+
:no-inherited-members:
110+
108111

109112
Parser
110113
------

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pytest-benchmark = "^3.4"
5050
pytest-cov = "^2.12"
5151
pytest-describe = "^2.0"
5252
pytest-timeout = "^1.4"
53+
typing-extensions = { version = "^3.10", python = "<3.8" }
5354
black = [
5455
{version = "21.9b0", python = ">=3.6.2"},
5556
{version = "20.8b1", python = "<3.6.2"}

src/graphql/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@
281281
# Types
282282
ExecutionContext,
283283
ExecutionResult,
284+
FormattedExecutionResult,
284285
# Middleware
285286
Middleware,
286287
MiddlewareManager,
@@ -341,9 +342,9 @@
341342
# Create, format, and print GraphQL errors.
342343
from .error import (
343344
GraphQLError,
345+
GraphQLFormattedError,
344346
GraphQLSyntaxError,
345347
located_error,
346-
format_error
347348
)
348349

349350
# Utilities for operating on GraphQL type schema and parsed sources.
@@ -637,6 +638,7 @@
637638
"get_directive_values",
638639
"ExecutionContext",
639640
"ExecutionResult",
641+
"FormattedExecutionResult",
640642
"Middleware",
641643
"MiddlewareManager",
642644
"subscribe",
@@ -684,9 +686,9 @@
684686
"NoDeprecatedCustomRule",
685687
"NoSchemaIntrospectionCustomRule",
686688
"GraphQLError",
689+
"GraphQLFormattedError",
687690
"GraphQLSyntaxError",
688691
"located_error",
689-
"format_error",
690692
"get_introspection_query",
691693
"get_operation_ast",
692694
"get_operation_root_type",

src/graphql/error/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
errors.
55
"""
66

7-
from .graphql_error import GraphQLError, format_error
7+
from .graphql_error import GraphQLError, GraphQLFormattedError
88

99
from .syntax_error import GraphQLSyntaxError
1010

1111
from .located_error import located_error
1212

1313
__all__ = [
1414
"GraphQLError",
15+
"GraphQLFormattedError",
1516
"GraphQLSyntaxError",
16-
"format_error",
17-
"located_error"
17+
"located_error",
1818
]

src/graphql/error/graphql_error.py

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
11
from sys import exc_info
22
from typing import Any, Collection, Dict, List, Optional, Union, TYPE_CHECKING
33

4+
try:
5+
from typing import TypedDict
6+
except ImportError: # Python < 3.8
7+
from typing_extensions import TypedDict
8+
49
if TYPE_CHECKING:
510
from ..language.ast import Node # noqa: F401
6-
from ..language.location import SourceLocation # noqa: F401
11+
from ..language.location import (
12+
SourceLocation,
13+
FormattedSourceLocation,
14+
) # noqa: F401
715
from ..language.source import Source # noqa: F401
816

9-
__all__ = ["GraphQLError"]
17+
__all__ = ["GraphQLError", "GraphQLFormattedError"]
18+
19+
20+
class GraphQLFormattedError(TypedDict, total=False):
21+
"""Formatted GraphQL error"""
22+
23+
# A short, human-readable summary of the problem that **SHOULD NOT** change
24+
# from occurrence to occurrence of the problem, except for purposes of localization.
25+
message: str
26+
# If an error can be associated to a particular point in the requested
27+
# GraphQL document, it should contain a list of locations.
28+
locations: List["FormattedSourceLocation"]
29+
# If an error can be associated to a particular field in the GraphQL result,
30+
# it _must_ contain an entry with the key `path` that details the path of
31+
# the response field which experienced the error. This allows clients to
32+
# identify whether a null result is intentional or caused by a runtime error.
33+
path: List[Union[str, int]]
34+
# Reserved for implementors to extend the protocol however they see fit,
35+
# and hence there are no additional restrictions on its contents.
36+
extensions: Dict[str, Any]
1037

1138

1239
class GraphQLError(Exception):
@@ -186,23 +213,21 @@ def __ne__(self, other: Any) -> bool:
186213
return not self == other
187214

188215
@property
189-
def formatted(self) -> Dict[str, Any]:
216+
def formatted(self) -> GraphQLFormattedError:
190217
"""Get error formatted according to the specification.
191218
192219
Given a GraphQLError, format it according to the rules described by the
193220
"Response Format, Errors" section of the GraphQL Specification.
194221
"""
195-
formatted: Dict[str, Any] = dict( # noqa: E701 (pycqa/flake8#394)
196-
message=self.message or "An unknown error occurred.",
197-
locations=(
198-
[location.formatted for location in self.locations]
199-
if self.locations is not None
200-
else None
201-
),
202-
path=self.path,
203-
)
222+
formatted: GraphQLFormattedError = {
223+
"message": self.message or "An unknown error occurred.",
224+
}
225+
if self.locations is not None:
226+
formatted["locations"] = [location.formatted for location in self.locations]
227+
if self.path is not None:
228+
formatted["path"] = self.path
204229
if self.extensions:
205-
formatted.update(extensions=self.extensions)
230+
formatted["extensions"] = self.extensions
206231
return formatted
207232

208233

@@ -219,7 +244,7 @@ def print_error(error: GraphQLError) -> str:
219244
return str(error)
220245

221246

222-
def format_error(error: GraphQLError) -> Dict[str, Any]:
247+
def format_error(error: GraphQLError) -> GraphQLFormattedError:
223248
"""Format a GraphQL error.
224249
225250
Given a GraphQLError, format it according to the rules described by the "Response

src/graphql/execution/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
default_type_resolver,
1212
ExecutionContext,
1313
ExecutionResult,
14+
FormattedExecutionResult,
1415
Middleware,
1516
)
1617

@@ -25,6 +26,7 @@
2526
"default_type_resolver",
2627
"ExecutionContext",
2728
"ExecutionResult",
29+
"FormattedExecutionResult",
2830
"Middleware",
2931
"MiddlewareManager",
3032
"get_directive_values",

src/graphql/execution/execute.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
cast,
1717
)
1818

19-
from ..error import GraphQLError, located_error
19+
try:
20+
from typing import TypedDict
21+
except ImportError: # Python < 3.8
22+
from typing_extensions import TypedDict
23+
24+
from ..error import GraphQLError, GraphQLFormattedError, located_error
2025
from ..language import (
2126
DocumentNode,
2227
FieldNode,
@@ -69,6 +74,7 @@
6974
"get_field_def",
7075
"ExecutionResult",
7176
"ExecutionContext",
77+
"FormattedExecutionResult",
7278
"Middleware",
7379
]
7480

@@ -92,6 +98,14 @@
9298
# 3) inline fragment "spreads" e.g. "...on Type { a }"
9399

94100

101+
class FormattedExecutionResult(TypedDict, total=False):
102+
"""Formatted execution result"""
103+
104+
errors: List[GraphQLFormattedError]
105+
data: Optional[Dict[str, Any]]
106+
extensions: Dict[str, Any]
107+
108+
95109
class ExecutionResult:
96110
"""The result of GraphQL execution.
97111
@@ -125,14 +139,14 @@ def __iter__(self) -> Iterable[Any]:
125139
return iter((self.data, self.errors))
126140

127141
@property
128-
def formatted(self) -> Dict[str, Any]:
142+
def formatted(self) -> FormattedExecutionResult:
129143
"""Get execution result formatted according to the specification."""
130-
errors = (
131-
None if self.errors is None else [error.formatted for error in self.errors]
132-
)
133-
if self.extensions is None:
134-
return dict(data=self.data, errors=errors)
135-
return dict(data=self.data, errors=errors, extensions=self.extensions)
144+
formatted: FormattedExecutionResult = {"data": self.data}
145+
if self.errors is not None:
146+
formatted["errors"] = [error.formatted for error in self.errors]
147+
if self.extensions is not None:
148+
formatted["extensions"] = self.extensions
149+
return formatted
136150

137151
def __eq__(self, other: Any) -> bool:
138152
if isinstance(other, dict):

src/graphql/language/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from .source import Source
88

9-
from .location import get_location, SourceLocation
9+
from .location import get_location, SourceLocation, FormattedSourceLocation
1010

1111
from .print_location import print_location, print_source_location
1212

@@ -111,6 +111,7 @@
111111
__all__ = [
112112
"get_location",
113113
"SourceLocation",
114+
"FormattedSourceLocation",
114115
"print_location",
115116
"print_source_location",
116117
"TokenKind",

src/graphql/language/location.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
from typing import Any, Dict, NamedTuple, TYPE_CHECKING
1+
from typing import Any, NamedTuple, TYPE_CHECKING
2+
3+
try:
4+
from typing import TypedDict
5+
except ImportError: # Python < 3.8
6+
from typing_extensions import TypedDict
27

38
if TYPE_CHECKING:
49
from .source import Source # noqa: F401
510

6-
__all__ = ["get_location", "SourceLocation"]
11+
__all__ = ["get_location", "SourceLocation", "FormattedSourceLocation"]
12+
13+
14+
class FormattedSourceLocation(TypedDict):
15+
"""Formatted source location"""
16+
17+
line: int
18+
column: int
719

820

921
class SourceLocation(NamedTuple):
@@ -13,7 +25,7 @@ class SourceLocation(NamedTuple):
1325
column: int
1426

1527
@property
16-
def formatted(self) -> Dict[str, int]:
28+
def formatted(self) -> FormattedSourceLocation:
1729
return dict(line=self.line, column=self.column)
1830

1931
def __eq__(self, other: Any) -> bool:

tests/benchmarks/test_execution_async.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,12 @@ async def resolve_user(obj, info):
3838

3939

4040
def test_execute_basic_async(benchmark):
41-
result = benchmark(
42-
lambda: asyncio.run(graphql(schema, "query { user { id, name }}"))
43-
)
41+
try:
42+
run = asyncio.run
43+
except AttributeError: # Python < 3.7
44+
loop = asyncio.get_event_loop()
45+
run = loop.run_until_complete # type: ignore
46+
result = benchmark(lambda: run(graphql(schema, "query { user { id, name }}")))
4447
assert not result.errors
4548
assert result.data == {
4649
"user": {

0 commit comments

Comments
 (0)