Skip to content

Path fix #186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions graphql/error/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@


class GraphQLError(Exception):
__slots__ = 'message', 'nodes', 'stack', 'original_error', '_source', '_positions', '_locations'
__slots__ = 'message', 'nodes', 'stack', 'original_error', '_source', '_positions', '_locations', 'path'

def __init__(self, message, nodes=None, stack=None, source=None, positions=None, locations=None):
def __init__(self, message, nodes=None, stack=None, source=None, positions=None, locations=None, path=None):
super(GraphQLError, self).__init__(message)
self.message = message
self.nodes = nodes
self.stack = stack
self._source = source
self._positions = positions
self._locations = locations
self.path = path

@property
def source(self):
Expand Down
2 changes: 2 additions & 0 deletions graphql/error/format_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ def format_error(error):
{'line': loc.line, 'column': loc.column}
for loc in error.locations
]
if error.path is not None:
formatted_error['path'] = error.path

return formatted_error
5 changes: 3 additions & 2 deletions graphql/error/located_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class GraphQLLocatedError(GraphQLError):

def __init__(self, nodes, original_error=None):
def __init__(self, nodes, original_error=None, path=None):
if original_error:
try:
message = str(original_error)
Expand All @@ -24,6 +24,7 @@ def __init__(self, nodes, original_error=None):
super(GraphQLLocatedError, self).__init__(
message=message,
nodes=nodes,
stack=stack
stack=stack,
path=path
)
self.original_error = original_error
72 changes: 37 additions & 35 deletions graphql/execution/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def execute_operation(exe_context, operation, root_value):
)

if operation.operation == 'mutation':
return execute_fields_serially(exe_context, type, root_value, fields)
return execute_fields_serially(exe_context, type, root_value, [], fields)

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

return execute_fields(exe_context, type, root_value, fields, None)
return execute_fields(exe_context, type, root_value, fields, [], None)


def execute_fields_serially(exe_context, parent_type, source_value, fields):
def execute_fields_serially(exe_context, parent_type, source_value, path, fields):
def execute_field_callback(results, response_name):
field_asts = fields[response_name]
result = resolve_field(
Expand All @@ -147,7 +147,7 @@ def execute_field_callback(results, response_name):
source_value,
field_asts,
None,
[response_name]
path+[response_name]
)
if result is Undefined:
return results
Expand All @@ -168,13 +168,13 @@ def execute_field(prev_promise, response_name):
return functools.reduce(execute_field, fields.keys(), Promise.resolve(collections.OrderedDict()))


def execute_fields(exe_context, parent_type, source_value, fields, info):
def execute_fields(exe_context, parent_type, source_value, fields, path, info):
contains_promise = False

final_results = OrderedDict()

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

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

for response_name, field_asts in fields.items():

result = subscribe_field(exe_context, parent_type, source_value, field_asts)
result = subscribe_field(exe_context, parent_type, source_value, field_asts, [response_name])
if result is Undefined:
continue

Expand Down Expand Up @@ -272,11 +271,12 @@ def resolve_field(exe_context, parent_type, source, field_asts, parent_info, fie
return_type,
field_asts,
info,
field_path,
result
)


def subscribe_field(exe_context, parent_type, source, field_asts):
def subscribe_field(exe_context, parent_type, source, field_asts, path):
field_ast = field_asts[0]
field_name = field_ast.name.value

Expand Down Expand Up @@ -312,7 +312,7 @@ def subscribe_field(exe_context, parent_type, source, field_asts):
operation=exe_context.operation,
variable_values=exe_context.variable_values,
context=context,
path=[field_name]
path=path
)

executor = exe_context.executor
Expand All @@ -332,6 +332,7 @@ def subscribe_field(exe_context, parent_type, source, field_asts):
return_type,
field_asts,
info,
path,
))


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


def complete_value_catching_error(exe_context, return_type, field_asts, info, result):
def complete_value_catching_error(exe_context, return_type, field_asts, info, path, result):
# If the field type is non-nullable, then it is resolved without any
# protection from errors.
if isinstance(return_type, GraphQLNonNull):
return complete_value(exe_context, return_type, field_asts, info, result)
return complete_value(exe_context, return_type, field_asts, info, path, result)

# Otherwise, error protection is applied, logging the error and
# resolving a null value for this field if one is encountered.
try:
completed = complete_value(exe_context, return_type, field_asts, info, result)
completed = complete_value(exe_context, return_type, field_asts, info, path, result)
if is_thenable(completed):
def handle_error(error):
traceback = completed._traceback
Expand All @@ -371,7 +372,7 @@ def handle_error(error):
return None


def complete_value(exe_context, return_type, field_asts, info, result):
def complete_value(exe_context, return_type, field_asts, info, path, result):
"""
Implements the instructions for completeValue as defined in the
"Field entries" section of the spec.
Expand Down Expand Up @@ -399,43 +400,44 @@ def complete_value(exe_context, return_type, field_asts, info, result):
return_type,
field_asts,
info,
path,
resolved
),
lambda error: Promise.rejected(
GraphQLLocatedError(field_asts, original_error=error))
GraphQLLocatedError(field_asts, original_error=error, path=path))
)

# print return_type, type(result)
if isinstance(result, Exception):
raise GraphQLLocatedError(field_asts, original_error=result)
raise GraphQLLocatedError(field_asts, original_error=result, path=path)

if isinstance(return_type, GraphQLNonNull):
return complete_nonnull_value(exe_context, return_type, field_asts, info, result)
return complete_nonnull_value(exe_context, return_type, field_asts, info, path, result)

# If result is null-like, return null.
if result is None:
return None

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

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

if isinstance(return_type, (GraphQLInterfaceType, GraphQLUnionType)):
return complete_abstract_value(exe_context, return_type, field_asts, info, result)
return complete_abstract_value(exe_context, return_type, field_asts, info, path, result)

if isinstance(return_type, GraphQLObjectType):
return complete_object_value(exe_context, return_type, field_asts, info, result)
return complete_object_value(exe_context, return_type, field_asts, info, path, result)

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


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

index = 0
path = info.path[:]
for item in result:
info.path = path + [index]
completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, item)
completed_item = complete_value_catching_error(exe_context, item_type, field_asts, info, path + [index], item, )
if not contains_promise and is_thenable(completed_item):
contains_promise = True

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


def complete_leaf_value(return_type, result):
def complete_leaf_value(return_type, path, result):
"""
Complete a Scalar or Enum by serializing to a valid value, returning null if serialization is not possible.
"""
Expand All @@ -471,12 +471,13 @@ def complete_leaf_value(return_type, result):
if serialized_result is None:
raise GraphQLError(
('Expected a value of type "{}" but ' +
'received: {}').format(return_type, result)
'received: {}').format(return_type, result),
path=path
)
return serialized_result


def complete_abstract_value(exe_context, return_type, field_asts, info, result):
def complete_abstract_value(exe_context, return_type, field_asts, info, path, result):
"""
Complete an value of an abstract type by determining the runtime type of that value, then completing based
on that type.
Expand Down Expand Up @@ -514,7 +515,7 @@ def complete_abstract_value(exe_context, return_type, field_asts, info, result):
field_asts
)

return complete_object_value(exe_context, runtime_type, field_asts, info, result)
return complete_object_value(exe_context, runtime_type, field_asts, info, path, result)


def get_default_resolve_type_fn(value, info, abstract_type):
Expand All @@ -524,7 +525,7 @@ def get_default_resolve_type_fn(value, info, abstract_type):
return type


def complete_object_value(exe_context, return_type, field_asts, info, result):
def complete_object_value(exe_context, return_type, field_asts, info, path, result):
"""
Complete an Object value by evaluating all sub-selections.
"""
Expand All @@ -537,21 +538,22 @@ def complete_object_value(exe_context, return_type, field_asts, info, result):

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


def complete_nonnull_value(exe_context, return_type, field_asts, info, result):
def complete_nonnull_value(exe_context, return_type, field_asts, info, path, result):
"""
Complete a NonNull value by completing the inner type
"""
completed = complete_value(
exe_context, return_type.of_type, field_asts, info, result
exe_context, return_type.of_type, field_asts, info, path, result
)
if completed is None:
raise GraphQLError(
'Cannot return null for non-nullable field {}.{}.'.format(
info.parent_type, info.field_name),
field_asts
field_asts,
path=path
)

return completed
6 changes: 5 additions & 1 deletion graphql/execution/tests/test_executor_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ def resolver_2(context, *_):

result = execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor())
formatted_errors = list(map(format_error, result.errors))
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
assert formatted_errors == [{
'locations': [{'line': 1, 'column': 20}],
'path': ['b'],
'message': 'resolver_2 failed!'
}]
assert result.data == {'a': 'hey', 'b': None}


Expand Down
6 changes: 5 additions & 1 deletion graphql/execution/tests/test_executor_gevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ def resolver_2(context, *_):

result = execute(GraphQLSchema(Type), ast, executor=GeventExecutor())
formatted_errors = list(map(format_error, result.errors))
assert formatted_errors == [{'locations': [{'line': 1, 'column': 20}], 'message': 'resolver_2 failed!'}]
assert formatted_errors == [{
'locations': [{'line': 1, 'column': 20}],
'path': ['b'],
'message': 'resolver_2 failed!'
}]
assert result.data == {'a': 'hey', 'b': None}


Expand Down
14 changes: 7 additions & 7 deletions graphql/execution/tests/test_executor_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,13 @@ def handle_results(result):
'syncReturnErrorList': ['sync0', None, 'sync2', None]
}
assert sorted(list(map(format_error, result.errors)), key=sort_key) == sorted([
{'locations': [{'line': 4, 'column': 9}], 'message': 'Error getting syncError'},
{'locations': [{'line': 5, 'column': 9}], 'message': 'Error getting syncReturnError'},
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList1'},
{'locations': [{'line': 6, 'column': 9}], 'message': 'Error getting syncReturnErrorList3'},
{'locations': [{'line': 8, 'column': 9}], 'message': 'Error getting asyncReject'},
{'locations': [{'line': 9, 'column': 9}], 'message': 'An unknown error occurred.'},
{'locations': [{'line': 10, 'column': 9}], 'message': 'Error getting asyncReturnError'}
{'locations': [{'line': 4, 'column': 9}], 'path':['syncError'], 'message': 'Error getting syncError'},
{'locations': [{'line': 5, 'column': 9}], 'path':['syncReturnError'], 'message': 'Error getting syncReturnError'},
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 1], 'message': 'Error getting syncReturnErrorList1'},
{'locations': [{'line': 6, 'column': 9}], 'path':['syncReturnErrorList', 3], 'message': 'Error getting syncReturnErrorList3'},
{'locations': [{'line': 8, 'column': 9}], 'path':['asyncReject'], 'message': 'Error getting asyncReject'},
{'locations': [{'line': 9, 'column': 9}], 'path':['asyncEmptyReject'], 'message': 'An unknown error occurred.'},
{'locations': [{'line': 10, 'column': 9}], 'path':['asyncReturnError'], 'message': 'Error getting asyncReturnError'}
], key=sort_key)

handle_results(execute(schema, ast, Data(), executor=ThreadExecutor()))
Expand Down
Loading