Skip to content

Commit 82eb322

Browse files
committed
pyutils: Add generic Path implementation
Replicates graphql/graphql-js@52820eb
1 parent 6100755 commit 82eb322

File tree

15 files changed

+106
-81
lines changed

15 files changed

+106
-81
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The current version 3.0.0a2 of GraphQL-core is up-to-date
1616
with GraphQL.js version 14.4.2.
1717

1818
All parts of the API are covered by an extensive test suite
19-
of currently 1915 unit tests.
19+
of currently 1918 unit tests.
2020

2121

2222
## Documentation

docs/modules/execution.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ Execution
77

88
.. autofunction:: execute
99
.. autofunction:: default_field_resolver
10-
.. autofunction:: response_path_as_list
1110

1211
.. autoclass:: ExecutionContext
1312
.. autoclass:: ExecutionResult

docs/modules/pyutils.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,20 @@ PyUtils
1111
.. autofunction:: dedent
1212
.. autofunction:: did_you_mean
1313
.. autoclass:: EventEmitter
14+
:members:
1415
.. autoclass:: EventEmitterAsyncIterator
16+
:members:
1517
.. autofunction:: identity_func
1618
.. autofunction:: inspect
1719
.. autofunction:: is_finite
1820
.. autofunction:: is_integer
1921
.. autofunction:: is_invalid
2022
.. autofunction:: is_nullish
2123
.. autoclass:: AwaitableOrValue
24+
:members:
2225
.. autofunction:: suggestion_list
2326
.. autoclass:: FrozenError
2427
.. autoclass:: FrozenList
2528
.. autoclass:: FrozenDict
29+
.. autoclass:: Path
30+
:members:

docs/modules/type.rst

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ Resolvers
100100
.. autoclass:: GraphQLIsTypeOfFn
101101
.. autoclass:: GraphQLResolveInfo
102102
.. autoclass:: GraphQLTypeResolver
103-
.. autoclass:: ResponsePath
104103

105104

106105
Directives

src/graphql/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@
263263
execute,
264264
default_field_resolver,
265265
default_type_resolver,
266-
response_path_as_list,
267266
get_directive_values,
268267
# Types
269268
ExecutionContext,
@@ -589,7 +588,6 @@
589588
"execute",
590589
"default_field_resolver",
591590
"default_type_resolver",
592-
"response_path_as_list",
593591
"get_directive_values",
594592
"ExecutionContext",
595593
"ExecutionResult",

src/graphql/execution/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
execute,
99
default_field_resolver,
1010
default_type_resolver,
11-
response_path_as_list,
1211
ExecutionContext,
1312
ExecutionResult,
1413
Middleware,
@@ -22,7 +21,6 @@
2221
"execute",
2322
"default_field_resolver",
2423
"default_type_resolver",
25-
"response_path_as_list",
2624
"ExecutionContext",
2725
"ExecutionResult",
2826
"Middleware",

src/graphql/execution/execute.py

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@
2626
OperationType,
2727
SelectionSetNode,
2828
)
29-
from ..pyutils import inspect, is_invalid, is_nullish, AwaitableOrValue, FrozenList
29+
from ..pyutils import (
30+
inspect,
31+
is_invalid,
32+
is_nullish,
33+
AwaitableOrValue,
34+
FrozenList,
35+
Path,
36+
)
3037
from ..utilities import get_operation_root_type, type_from_ast
3138
from ..type import (
3239
GraphQLAbstractType,
@@ -42,7 +49,6 @@
4249
GraphQLFieldResolver,
4350
GraphQLTypeResolver,
4451
GraphQLResolveInfo,
45-
ResponsePath,
4652
SchemaMetaFieldDef,
4753
TypeMetaFieldDef,
4854
TypeNameMetaFieldDef,
@@ -57,13 +63,11 @@
5763
from .values import get_argument_values, get_directive_values, get_variable_values
5864

5965
__all__ = [
60-
"add_path",
6166
"assert_valid_execution_arguments",
6267
"default_field_resolver",
6368
"default_type_resolver",
6469
"execute",
6570
"get_field_def",
66-
"response_path_as_list",
6771
"ExecutionResult",
6872
"ExecutionContext",
6973
"Middleware",
@@ -100,6 +104,7 @@ class ExecutionResult(NamedTuple):
100104
errors: Optional[List[GraphQLError]]
101105

102106

107+
# noinspection PyTypeHints
103108
ExecutionResult.__new__.__defaults__ = (None, None) # type: ignore
104109

105110
Middleware = Optional[Union[Tuple, List, MiddlewareManager]]
@@ -377,7 +382,7 @@ def execute_fields_serially(
377382
self,
378383
parent_type: GraphQLObjectType,
379384
source_value: Any,
380-
path: Optional[ResponsePath],
385+
path: Optional[Path],
381386
fields: Dict[str, List[FieldNode]],
382387
) -> AwaitableOrValue[Dict[str, Any]]:
383388
"""Execute the given fields serially.
@@ -386,7 +391,7 @@ def execute_fields_serially(
386391
"""
387392
results: Dict[str, Any] = {}
388393
for response_name, field_nodes in fields.items():
389-
field_path = add_path(path, response_name)
394+
field_path = Path(path, response_name)
390395
result = self.resolve_field(
391396
parent_type, source_value, field_nodes, field_path
392397
)
@@ -427,7 +432,7 @@ def execute_fields(
427432
self,
428433
parent_type: GraphQLObjectType,
429434
source_value: Any,
430-
path: Optional[ResponsePath],
435+
path: Optional[Path],
431436
fields: Dict[str, List[FieldNode]],
432437
) -> AwaitableOrValue[Dict[str, Any]]:
433438
"""Execute the given fields concurrently.
@@ -438,7 +443,7 @@ def execute_fields(
438443
awaitable_fields: List[str] = []
439444
append_awaitable = awaitable_fields.append
440445
for response_name, field_nodes in fields.items():
441-
field_path = add_path(path, response_name)
446+
field_path = Path(path, response_name)
442447
result = self.resolve_field(
443448
parent_type, source_value, field_nodes, field_path
444449
)
@@ -559,7 +564,7 @@ def build_resolve_info(
559564
field_def: GraphQLField,
560565
field_nodes: List[FieldNode],
561566
parent_type: GraphQLObjectType,
562-
path: ResponsePath,
567+
path: Path,
563568
) -> GraphQLResolveInfo:
564569
# The resolve function's first argument is a collection of information about
565570
# the current execution state.
@@ -582,7 +587,7 @@ def resolve_field(
582587
parent_type: GraphQLObjectType,
583588
source: Any,
584589
field_nodes: List[FieldNode],
585-
path: ResponsePath,
590+
path: Path,
586591
) -> AwaitableOrValue[Any]:
587592
"""Resolve the field on the given source object.
588593
@@ -652,7 +657,7 @@ def complete_value_catching_error(
652657
return_type: GraphQLOutputType,
653658
field_nodes: List[FieldNode],
654659
info: GraphQLResolveInfo,
655-
path: ResponsePath,
660+
path: Path,
656661
result: Any,
657662
) -> AwaitableOrValue[Any]:
658663
"""Complete a value while catching an error.
@@ -694,10 +699,10 @@ def handle_field_error(
694699
self,
695700
raw_error: Exception,
696701
field_nodes: List[FieldNode],
697-
path: ResponsePath,
702+
path: Path,
698703
return_type: GraphQLOutputType,
699704
) -> None:
700-
error = located_error(raw_error, field_nodes, response_path_as_list(path))
705+
error = located_error(raw_error, field_nodes, path.as_list())
701706

702707
# If the field type is non-nullable, then it is resolved without any protection
703708
# from errors, however it still properly locates the error.
@@ -713,7 +718,7 @@ def complete_value(
713718
return_type: GraphQLOutputType,
714719
field_nodes: List[FieldNode],
715720
info: GraphQLResolveInfo,
716-
path: ResponsePath,
721+
path: Path,
717722
result: Any,
718723
) -> AwaitableOrValue[Any]:
719724
"""Complete a value.
@@ -797,7 +802,7 @@ def complete_list_value(
797802
return_type: GraphQLList[GraphQLOutputType],
798803
field_nodes: List[FieldNode],
799804
info: GraphQLResolveInfo,
800-
path: ResponsePath,
805+
path: Path,
801806
result: Iterable[Any],
802807
) -> AwaitableOrValue[Any]:
803808
"""Complete a list value.
@@ -821,7 +826,7 @@ def complete_list_value(
821826
for index, item in enumerate(result):
822827
# No need to modify the info object containing the path, since from here on
823828
# it is not ever accessed by resolver functions.
824-
field_path = add_path(path, index)
829+
field_path = path.add_key(index)
825830
completed_item = self.complete_value_catching_error(
826831
item_type, field_nodes, info, field_path, item
827832
)
@@ -866,7 +871,7 @@ def complete_abstract_value(
866871
return_type: GraphQLAbstractType,
867872
field_nodes: List[FieldNode],
868873
info: GraphQLResolveInfo,
869-
path: ResponsePath,
874+
path: Path,
870875
result: Any,
871876
) -> AwaitableOrValue[Any]:
872877
"""Complete an abstract value.
@@ -947,7 +952,7 @@ def complete_object_value(
947952
return_type: GraphQLObjectType,
948953
field_nodes: List[FieldNode],
949954
info: GraphQLResolveInfo,
950-
path: ResponsePath,
955+
path: Path,
951956
result: Any,
952957
) -> AwaitableOrValue[Dict[str, Any]]:
953958
"""Complete an Object value by executing all sub-selections."""
@@ -981,7 +986,7 @@ def collect_and_execute_subfields(
981986
self,
982987
return_type: GraphQLObjectType,
983988
field_nodes: List[FieldNode],
984-
path: ResponsePath,
989+
path: Path,
985990
result: Any,
986991
) -> AwaitableOrValue[Dict[str, Any]]:
987992
"""Collect sub-fields to execute to complete this value."""
@@ -1041,29 +1046,6 @@ def assert_valid_execution_arguments(
10411046
)
10421047

10431048

1044-
def response_path_as_list(path: ResponsePath) -> List[Union[str, int]]:
1045-
"""Get response path as a list.
1046-
1047-
Given a ResponsePath (found in the `path` entry in the information provided as the
1048-
last argument to a field resolver), return a list of the path keys.
1049-
"""
1050-
flattened: List[Union[str, int]] = []
1051-
append = flattened.append
1052-
curr: Optional[ResponsePath] = path
1053-
while curr:
1054-
append(curr.key)
1055-
curr = curr.prev
1056-
return flattened[::-1]
1057-
1058-
1059-
def add_path(prev: Optional[ResponsePath], key: Union[str, int]) -> ResponsePath:
1060-
"""Add a key to a response path.
1061-
1062-
Given a ResponsePath and a key, return a new ResponsePath containing the new key.
1063-
"""
1064-
return ResponsePath(prev, key)
1065-
1066-
10671049
def get_field_def(
10681050
schema: GraphQLSchema, parent_type: GraphQLObjectType, field_name: str
10691051
) -> GraphQLField:

src/graphql/pyutils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .frozen_error import FrozenError
2525
from .frozen_list import FrozenList
2626
from .frozen_dict import FrozenDict
27+
from .path import Path
2728

2829
__all__ = [
2930
"camel_to_snake",
@@ -44,4 +45,5 @@
4445
"FrozenError",
4546
"FrozenList",
4647
"FrozenDict",
48+
"Path",
4749
]

src/graphql/pyutils/path.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from typing import Any, List, NamedTuple, Union
2+
3+
__all__ = ["Path"]
4+
5+
6+
class Path(NamedTuple):
7+
"""A generic path of string or integer indices"""
8+
9+
prev: Any # Optional['Path'] (python/mypy/issues/731)
10+
"""path with the previous indices"""
11+
key: Union[str, int]
12+
"""current index in the path (string or integer)"""
13+
14+
def add_key(self, key: Union[str, int]) -> "Path":
15+
"""Return a new Path containing the given key."""
16+
return Path(self, key)
17+
18+
def as_list(self) -> List[Union[str, int]]:
19+
"""Return a list of the path keys."""
20+
flattened: List[Union[str, int]] = []
21+
append = flattened.append
22+
curr: Path = self
23+
while curr:
24+
append(curr.key)
25+
curr = curr.prev
26+
return flattened[::-1]

src/graphql/subscription/subscribe.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33

44
from ..error import GraphQLError, located_error
55
from ..execution.execute import (
6-
add_path,
76
assert_valid_execution_arguments,
87
execute,
98
get_field_def,
10-
response_path_as_list,
119
ExecutionContext,
1210
ExecutionResult,
1311
)
1412
from ..language import DocumentNode
13+
from ..pyutils import Path
1514
from ..type import GraphQLFieldResolver, GraphQLSchema
1615
from ..utilities import get_operation_root_type
1716
from .map_async_iterator import MapAsyncIterator
@@ -153,7 +152,7 @@ async def create_source_event_stream(
153152
# AsyncIterable yielding raw payloads.
154153
resolve_fn = field_def.subscribe or context.field_resolver
155154

156-
path = add_path(None, response_name)
155+
path = Path(None, response_name)
157156

158157
info = context.build_resolve_info(field_def, field_nodes, type_, path)
159158

@@ -166,7 +165,7 @@ async def create_source_event_stream(
166165
event_stream = await cast(Awaitable, result) if isawaitable(result) else result
167166
# If `event_stream` is an Error, rethrow a located error.
168167
if isinstance(event_stream, Exception):
169-
raise located_error(event_stream, field_nodes, response_path_as_list(path))
168+
raise located_error(event_stream, field_nodes, path.as_list())
170169

171170
# Assert field returned an event stream, otherwise yield an error.
172171
if isinstance(event_stream, AsyncIterable):

src/graphql/type/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
The :mod:`graphql.type` package is responsible for defining GraphQL types and schema.
44
"""
55

6+
from ..pyutils import Path as ResponsePath
7+
68
from .schema import (
79
# Predicate
810
is_schema,
@@ -91,7 +93,6 @@
9193
GraphQLTypeResolver,
9294
GraphQLIsTypeOfFn,
9395
GraphQLResolveInfo,
94-
ResponsePath,
9596
)
9697

9798
from .directives import (

0 commit comments

Comments
 (0)