Skip to content

Commit 490d056

Browse files
authored
Merge pull request #186 from graphql-python/path-fix
Path fix
2 parents ea0dd0c + c9f1345 commit 490d056

11 files changed

+196
-72
lines changed

graphql/error/base.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33

44

55
class GraphQLError(Exception):
6-
__slots__ = 'message', 'nodes', 'stack', 'original_error', '_source', '_positions', '_locations'
6+
__slots__ = 'message', 'nodes', 'stack', 'original_error', '_source', '_positions', '_locations', 'path'
77

8-
def __init__(self, message, nodes=None, stack=None, source=None, positions=None, locations=None):
8+
def __init__(self, message, nodes=None, stack=None, source=None, positions=None, locations=None, path=None):
99
super(GraphQLError, self).__init__(message)
1010
self.message = message
1111
self.nodes = nodes
1212
self.stack = stack
1313
self._source = source
1414
self._positions = positions
1515
self._locations = locations
16+
self.path = path
1617

1718
@property
1819
def source(self):

graphql/error/format_error.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ def format_error(error):
1111
{'line': loc.line, 'column': loc.column}
1212
for loc in error.locations
1313
]
14+
if error.path is not None:
15+
formatted_error['path'] = error.path
1416

1517
return formatted_error

graphql/error/located_error.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class GraphQLLocatedError(GraphQLError):
99

10-
def __init__(self, nodes, original_error=None):
10+
def __init__(self, nodes, original_error=None, path=None):
1111
if original_error:
1212
try:
1313
message = str(original_error)
@@ -24,6 +24,7 @@ def __init__(self, nodes, original_error=None):
2424
super(GraphQLLocatedError, self).__init__(
2525
message=message,
2626
nodes=nodes,
27-
stack=stack
27+
stack=stack,
28+
path=path
2829
)
2930
self.original_error = original_error

graphql/execution/executor.py

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def execute_operation(exe_context, operation, root_value):
124124
)
125125

126126
if operation.operation == 'mutation':
127-
return execute_fields_serially(exe_context, type, root_value, fields)
127+
return execute_fields_serially(exe_context, type, root_value, [], fields)
128128

129129
if operation.operation == 'subscription':
130130
if not exe_context.allow_subscriptions:
@@ -133,12 +133,12 @@ def execute_operation(exe_context, operation, root_value):
133133
"You will need to either use the subscribe function "
134134
"or pass allow_subscriptions=True"
135135
)
136-
return subscribe_fields(exe_context, type, root_value, fields)
136+
return subscribe_fields(exe_context, type, root_value, fields,)
137137

138-
return execute_fields(exe_context, type, root_value, fields, None)
138+
return execute_fields(exe_context, type, root_value, fields, [], None)
139139

140140

141-
def execute_fields_serially(exe_context, parent_type, source_value, fields):
141+
def execute_fields_serially(exe_context, parent_type, source_value, path, fields):
142142
def execute_field_callback(results, response_name):
143143
field_asts = fields[response_name]
144144
result = resolve_field(
@@ -147,7 +147,7 @@ def execute_field_callback(results, response_name):
147147
source_value,
148148
field_asts,
149149
None,
150-
[response_name]
150+
path+[response_name]
151151
)
152152
if result is Undefined:
153153
return results
@@ -168,13 +168,13 @@ def execute_field(prev_promise, response_name):
168168
return functools.reduce(execute_field, fields.keys(), Promise.resolve(collections.OrderedDict()))
169169

170170

171-
def execute_fields(exe_context, parent_type, source_value, fields, info):
171+
def execute_fields(exe_context, parent_type, source_value, fields, path, info):
172172
contains_promise = False
173173

174174
final_results = OrderedDict()
175175

176176
for response_name, field_asts in fields.items():
177-
result = resolve_field(exe_context, parent_type, source_value, field_asts, info, (info.path if info else []) + [response_name])
177+
result = resolve_field(exe_context, parent_type, source_value, field_asts, info, path + [response_name])
178178
if result is Undefined:
179179
continue
180180

@@ -207,8 +207,7 @@ def map_result(data):
207207
# assert len(fields) == 1, "Can only subscribe one element at a time."
208208

209209
for response_name, field_asts in fields.items():
210-
211-
result = subscribe_field(exe_context, parent_type, source_value, field_asts)
210+
result = subscribe_field(exe_context, parent_type, source_value, field_asts, [response_name])
212211
if result is Undefined:
213212
continue
214213

@@ -272,11 +271,12 @@ def resolve_field(exe_context, parent_type, source, field_asts, parent_info, fie
272271
return_type,
273272
field_asts,
274273
info,
274+
field_path,
275275
result
276276
)
277277

278278

279-
def subscribe_field(exe_context, parent_type, source, field_asts):
279+
def subscribe_field(exe_context, parent_type, source, field_asts, path):
280280
field_ast = field_asts[0]
281281
field_name = field_ast.name.value
282282

@@ -312,7 +312,7 @@ def subscribe_field(exe_context, parent_type, source, field_asts):
312312
operation=exe_context.operation,
313313
variable_values=exe_context.variable_values,
314314
context=context,
315-
path=[field_name]
315+
path=path
316316
)
317317

318318
executor = exe_context.executor
@@ -332,6 +332,7 @@ def subscribe_field(exe_context, parent_type, source, field_asts):
332332
return_type,
333333
field_asts,
334334
info,
335+
path,
335336
))
336337

337338

@@ -346,16 +347,16 @@ def resolve_or_error(resolve_fn, source, info, args, executor):
346347
return e
347348

348349

349-
def complete_value_catching_error(exe_context, return_type, field_asts, info, result):
350+
def complete_value_catching_error(exe_context, return_type, field_asts, info, path, result):
350351
# If the field type is non-nullable, then it is resolved without any
351352
# protection from errors.
352353
if isinstance(return_type, GraphQLNonNull):
353-
return complete_value(exe_context, return_type, field_asts, info, result)
354+
return complete_value(exe_context, return_type, field_asts, info, path, result)
354355

355356
# Otherwise, error protection is applied, logging the error and
356357
# resolving a null value for this field if one is encountered.
357358
try:
358-
completed = complete_value(exe_context, return_type, field_asts, info, result)
359+
completed = complete_value(exe_context, return_type, field_asts, info, path, result)
359360
if is_thenable(completed):
360361
def handle_error(error):
361362
traceback = completed._traceback
@@ -371,7 +372,7 @@ def handle_error(error):
371372
return None
372373

373374

374-
def complete_value(exe_context, return_type, field_asts, info, result):
375+
def complete_value(exe_context, return_type, field_asts, info, path, result):
375376
"""
376377
Implements the instructions for completeValue as defined in the
377378
"Field entries" section of the spec.
@@ -399,43 +400,44 @@ def complete_value(exe_context, return_type, field_asts, info, result):
399400
return_type,
400401
field_asts,
401402
info,
403+
path,
402404
resolved
403405
),
404406
lambda error: Promise.rejected(
405-
GraphQLLocatedError(field_asts, original_error=error))
407+
GraphQLLocatedError(field_asts, original_error=error, path=path))
406408
)
407409

408410
# print return_type, type(result)
409411
if isinstance(result, Exception):
410-
raise GraphQLLocatedError(field_asts, original_error=result)
412+
raise GraphQLLocatedError(field_asts, original_error=result, path=path)
411413

412414
if isinstance(return_type, GraphQLNonNull):
413-
return complete_nonnull_value(exe_context, return_type, field_asts, info, result)
415+
return complete_nonnull_value(exe_context, return_type, field_asts, info, path, result)
414416

415417
# If result is null-like, return null.
416418
if result is None:
417419
return None
418420

419421
# If field type is List, complete each item in the list with the inner type
420422
if isinstance(return_type, GraphQLList):
421-
return complete_list_value(exe_context, return_type, field_asts, info, result)
423+
return complete_list_value(exe_context, return_type, field_asts, info, path, result)
422424

423425
# If field type is Scalar or Enum, serialize to a valid value, returning
424426
# null if coercion is not possible.
425427
if isinstance(return_type, (GraphQLScalarType, GraphQLEnumType)):
426-
return complete_leaf_value(return_type, result)
428+
return complete_leaf_value(return_type, path, result)
427429

428430
if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)):
429-
return complete_abstract_value(exe_context, return_type, field_asts, info, result)
431+
return complete_abstract_value(exe_context, return_type, field_asts, info, path, result)
430432

431433
if isinstance(return_type, GraphQLObjectType):
432-
return complete_object_value(exe_context, return_type, field_asts, info, result)
434+
return complete_object_value(exe_context, return_type, field_asts, info, path, result)
433435

434436
assert False, u'Cannot complete value of unexpected type "{}".'.format(
435437
return_type)
436438

437439

438-
def complete_list_value(exe_context, return_type, field_asts, info, result):
440+
def complete_list_value(exe_context, return_type, field_asts, info, path, result):
439441
"""
440442
Complete a list value by completing each item in the list with the inner type
441443
"""
@@ -448,10 +450,8 @@ def complete_list_value(exe_context, return_type, field_asts, info, result):
448450
contains_promise = False
449451

450452
index = 0
451-
path = info.path[:]
452453
for item in result:
453-
info.path = path + [index]
454-
completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, item)
454+
completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, path + [index], item, )
455455
if not contains_promise and is_thenable(completed_item):
456456
contains_promise = True
457457

@@ -461,7 +461,7 @@ def complete_list_value(exe_context, return_type, field_asts, info, result):
461461
return Promise.all(completed_results) if contains_promise else completed_results
462462

463463

464-
def complete_leaf_value(return_type, result):
464+
def complete_leaf_value(return_type, path, result):
465465
"""
466466
Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible.
467467
"""
@@ -471,12 +471,13 @@ def complete_leaf_value(return_type, result):
471471
if serialized_result is None:
472472
raise GraphQLError(
473473
('Expected a value of type "{}" but ' +
474-
'received: {}').format(return_type, result)
474+
'received: {}').format(return_type, result),
475+
path=path
475476
)
476477
return serialized_result
477478

478479

479-
def complete_abstract_value(exe_context, return_type, field_asts, info, result):
480+
def complete_abstract_value(exe_context, return_type, field_asts, info, path, result):
480481
"""
481482
Complete an value of an abstract type by determining the runtime type of that value, then completing based
482483
on that type.
@@ -514,7 +515,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result):
514515
field_asts
515516
)
516517

517-
return complete_object_value(exe_context, runtime_type, field_asts, info, result)
518+
return complete_object_value(exe_context, runtime_type, field_asts, info, path, result)
518519

519520

520521
def get_default_resolve_type_fn(value, info, abstract_type):
@@ -524,7 +525,7 @@ def get_default_resolve_type_fn(value, info, abstract_type):
524525
return type
525526

526527

527-
def complete_object_value(exe_context, return_type, field_asts, info, result):
528+
def complete_object_value(exe_context, return_type, field_asts, info, path, result):
528529
"""
529530
Complete an Object value by evaluating all sub-selections.
530531
"""
@@ -537,21 +538,22 @@ def complete_object_value(exe_context, return_type, field_asts, info, result):
537538

538539
# Collect sub-fields to execute to complete this value.
539540
subfield_asts = exe_context.get_sub_fields(return_type, field_asts)
540-
return execute_fields(exe_context, return_type, result, subfield_asts, info)
541+
return execute_fields(exe_context, return_type, result, subfield_asts, path, info)
541542

542543

543-
def complete_nonnull_value(exe_context, return_type, field_asts, info, result):
544+
def complete_nonnull_value(exe_context, return_type, field_asts, info, path, result):
544545
"""
545546
Complete a NonNull value by completing the inner type
546547
"""
547548
completed = complete_value(
548-
exe_context, return_type.of_type, field_asts, info, result
549+
exe_context, return_type.of_type, field_asts, info, path, result
549550
)
550551
if completed is None:
551552
raise GraphQLError(
552553
'Cannot return null for non-nullable field {}.{}.'.format(
553554
info.parent_type, info.field_name),
554-
field_asts
555+
field_asts,
556+
path=path
555557
)
556558

557559
return completed

graphql/execution/tests/test_executor_asyncio.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ def resolver_2(context, *_):
8686

8787
result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor())
8888
formatted_errors = list(map(format_error, result.errors))
89-
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
89+
assert formatted_errors == [{
90+
'locations': [{'line': 1, 'column': 20}],
91+
'path': ['b'],
92+
'message': 'resolver_2 failed!'
93+
}]
9094
assert result.data == {'a': 'hey', 'b': None}
9195

9296

graphql/execution/tests/test_executor_gevent.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ def resolver_2(context, *_):
5959

6060
result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor())
6161
formatted_errors = list(map(format_error, result.errors))
62-
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
62+
assert formatted_errors == [{
63+
'locations': [{'line': 1, 'column': 20}],
64+
'path': ['b'],
65+
'message': 'resolver_2 failed!'
66+
}]
6367
assert result.data == {'a': 'hey', 'b': None}
6468

6569

graphql/execution/tests/test_executor_thread.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,13 @@ def handle_results(result):
206206
'syncReturnErrorList': ['sync0', None, 'sync2', None]
207207
}
208208
assert sorted(list(map(format_error, result.errors)), key=sort_key) == sorted([
209-
{'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'},
210-
{'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'},
211-
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'},
212-
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'},
213-
{'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'},
214-
{'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'},
215-
{'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'}
209+
{'locations': [{'line': 4, 'column': 9}], 'path':['syncError'], 'message': 'Error getting syncError'},
210+
{'locations': [{'line': 5, 'column': 9}], 'path':['syncReturnError'], 'message': 'Error getting syncReturnError'},
211+
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 1], 'message': 'Error getting syncReturnErrorList1'},
212+
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 3], 'message': 'Error getting syncReturnErrorList3'},
213+
{'locations': [{'line': 8, 'column': 9}], 'path':['asyncReject'], 'message': 'Error getting asyncReject'},
214+
{'locations': [{'line': 9, 'column': 9}], 'path':['asyncEmptyReject'], 'message': 'An unknown error occurred.'},
215+
{'locations': [{'line': 10, 'column': 9}], 'path':['asyncReturnError'], 'message': 'Error getting asyncReturnError'}
216216
], key=sort_key)
217217

218218
handle_results(execute(schema, ast, Data(), executor=ThreadExecutor()))

0 commit comments

Comments
 (0)