Skip to content

Commit 49c6732

Browse files
committed
Add parent type to path to avoid path ambiguity
Replicates graphql/graphql-js@b489677
1 parent a65ca17 commit 49c6732

File tree

6 files changed

+71
-16
lines changed

6 files changed

+71
-16
lines changed

src/graphql/execution/execute.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def execute_fields_serially(
369369
results: AwaitableOrValue[Dict[str, Any]] = {}
370370
is_awaitable = self.is_awaitable
371371
for response_name, field_nodes in fields.items():
372-
field_path = Path(path, response_name)
372+
field_path = Path(path, response_name, parent_type.name)
373373
result = self.resolve_field(
374374
parent_type, source_value, field_nodes, field_path
375375
)
@@ -430,7 +430,7 @@ def execute_fields(
430430
awaitable_fields: List[str] = []
431431
append_awaitable = awaitable_fields.append
432432
for response_name, field_nodes in fields.items():
433-
field_path = Path(path, response_name)
433+
field_path = Path(path, response_name, parent_type.name)
434434
result = self.resolve_field(
435435
parent_type, source_value, field_nodes, field_path
436436
)
@@ -825,7 +825,7 @@ def complete_list_value(
825825
for index, item in enumerate(result):
826826
# No need to modify the info object containing the path, since from here on
827827
# it is not ever accessed by resolver functions.
828-
field_path = path.add_key(index)
828+
field_path = path.add_key(index, None)
829829
completed_item = self.complete_value_catching_error(
830830
item_type, field_nodes, info, field_path, item
831831
)

src/graphql/pyutils/path.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, List, NamedTuple, Union
1+
from typing import Any, List, NamedTuple, Optional, Union
22

33
__all__ = ["Path"]
44

@@ -10,10 +10,12 @@ class Path(NamedTuple):
1010
"""path with the previous indices"""
1111
key: Union[str, int]
1212
"""current index in the path (string or integer)"""
13+
typename: Optional[str]
14+
"""name of the parent type to avoid path ambiguity"""
1315

14-
def add_key(self, key: Union[str, int]) -> "Path":
16+
def add_key(self, key: Union[str, int], typename: Optional[str] = None) -> "Path":
1517
"""Return a new Path containing the given key."""
16-
return Path(self, key)
18+
return Path(self, key, typename)
1719

1820
def as_list(self) -> List[Union[str, int]]:
1921
"""Return a list of the path keys."""

src/graphql/subscription/subscribe.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async def create_source_event_stream(
161161
# AsyncIterable yielding raw payloads.
162162
resolve_fn = field_def.subscribe or context.field_resolver
163163

164-
path = Path(None, response_name)
164+
path = Path(None, response_name, type_.name)
165165

166166
info = context.build_resolve_info(field_def, field_nodes, type_, path)
167167

src/graphql/utilities/coerce_input_value.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def coerce_input_value(
7171
for index, item_value in enumerate(input_value):
7272
append_item(
7373
coerce_input_value(
74-
item_value, item_type, on_error, Path(path, index)
74+
item_value, item_type, on_error, Path(path, index, None)
7575
)
7676
)
7777
return coerced_list
@@ -111,7 +111,7 @@ def coerce_input_value(
111111
continue
112112

113113
coerced_dict[field.out_name or field_name] = coerce_input_value(
114-
field_value, field.type, on_error, Path(path, field_name)
114+
field_value, field.type, on_error, Path(path, field_name, type_.name)
115115
)
116116

117117
# Ensure every provided field is defined.

tests/execution/test_executor.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from typing import cast, Awaitable
2+
from typing import cast, Any, Awaitable, Optional
33

44
from pytest import raises, mark # type: ignore
55

@@ -20,6 +20,7 @@
2020
GraphQLSchema,
2121
GraphQLScalarType,
2222
GraphQLString,
23+
GraphQLUnionType,
2324
ResponsePath,
2425
)
2526

@@ -302,7 +303,7 @@ def resolve(_obj, info):
302303
field_nodes=[field],
303304
return_type=GraphQLString,
304305
parent_type=cast(GraphQLObjectType, schema.query_type),
305-
path=ResponsePath(None, "result"),
306+
path=ResponsePath(None, "result", "Test"),
306307
schema=schema,
307308
fragments={},
308309
root_value=root_value,
@@ -312,6 +313,58 @@ def resolve(_obj, info):
312313
is_awaitable=resolved_infos[0].is_awaitable,
313314
)
314315

316+
def it_populates_path_correctly_with_complex_types():
317+
path: Optional[ResponsePath] = None
318+
319+
def resolve(_val, info):
320+
nonlocal path
321+
path = info.path
322+
323+
def resolve_type(_val, _info, _type):
324+
return "SomeObject"
325+
326+
some_object = GraphQLObjectType(
327+
"SomeObject", {"test": GraphQLField(GraphQLString, resolve=resolve)}
328+
)
329+
some_union = GraphQLUnionType(
330+
"SomeUnion", [some_object], resolve_type=resolve_type
331+
)
332+
test_type = GraphQLObjectType(
333+
"SomeQuery",
334+
{
335+
"test": GraphQLField(
336+
GraphQLNonNull(GraphQLList(GraphQLNonNull(some_union)))
337+
)
338+
},
339+
)
340+
schema = GraphQLSchema(test_type)
341+
root_value: Any = {"test": [{}]}
342+
document = parse(
343+
"""
344+
query {
345+
l1: test {
346+
... on SomeObject {
347+
l2: test
348+
}
349+
}
350+
}
351+
"""
352+
)
353+
354+
execute_sync(schema, document, root_value)
355+
356+
assert path is not None
357+
prev, key, typename = path
358+
assert key == "l2"
359+
assert typename == "SomeObject"
360+
prev, key, typename = prev
361+
assert key == 0
362+
assert typename is None
363+
prev, key, typename = prev
364+
assert key == "l1"
365+
assert typename == "SomeQuery"
366+
assert prev is None
367+
315368
def threads_root_value_context_correctly():
316369
resolved_values = []
317370

tests/pyutils/test_path.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,24 @@
33

44
def describe_path():
55
def add_path():
6-
path = Path(None, 0)
6+
path = Path(None, 0, None)
77
assert path.prev is None
88
assert path.key == 0
9-
prev, path = path, Path(path, 1)
9+
prev, path = path, Path(path, 1, None)
1010
assert path.prev is prev
1111
assert path.key == 1
12-
prev, path = path, Path(path, "two")
12+
prev, path = path, Path(path, "two", None)
1313
assert path.prev is prev
1414
assert path.key == "two"
1515

1616
def add_key():
17-
prev = Path(None, 0)
17+
prev = Path(None, 0, None)
1818
path = prev.add_key("one")
1919
assert path.prev is prev
2020
assert path.key == "one"
2121

2222
def as_list():
23-
path = Path(None, 1)
23+
path = Path(None, 1, None)
2424
assert path.as_list() == [1]
2525
path = path.add_key("two")
2626
assert path.as_list() == [1, "two"]

0 commit comments

Comments
 (0)