Skip to content

Improved error reporting #131

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 6 commits into from
Jul 23, 2017
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
2 changes: 1 addition & 1 deletion graphql/error/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ def locations(self):
source = self.source
if self.positions and source:
self._locations = [get_location(source, pos) for pos in self.positions]
return self._locations
return self._locations
10 changes: 7 additions & 3 deletions graphql/execution/base.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
import sys

from ..error import GraphQLError
from ..language import ast
from ..pyutils.default_ordered_dict import DefaultOrderedDict
from ..type.definition import GraphQLInterfaceType, GraphQLUnionType
from ..type.definition import Undefined, GraphQLInterfaceType, GraphQLUnionType
from ..type.directives import GraphQLIncludeDirective, GraphQLSkipDirective
from ..type.introspection import (SchemaMetaFieldDef, TypeMetaFieldDef,
TypeNameMetaFieldDef)
from ..utils.type_from_ast import type_from_ast
from .values import get_argument_values, get_variable_values

Undefined = object()


class ExecutionContext(object):
"""Data that must be available at all points during query execution.
Expand Down Expand Up @@ -82,6 +82,10 @@ def get_argument_values(self, field_def, field_ast):

return result

def report_error(self, error, traceback=None):
sys.excepthook(type(error), error, getattr(error, 'stack', None) or traceback)
self.errors.append(error)

def get_sub_fields(self, return_type, field_asts):
k = return_type, tuple(field_asts)
if k not in self._subfields_cache:
Expand Down
12 changes: 7 additions & 5 deletions graphql/execution/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ def execute(schema, document_ast, root_value=None, context_value=None,
middleware
)

def executor(resolve, reject):
return resolve(execute_operation(context, context.operation, root_value))
def executor(v):
return execute_operation(context, context.operation, root_value)

def on_rejected(error):
context.errors.append(error)
Expand All @@ -75,7 +75,7 @@ def on_resolve(data):
return ExecutionResult(data=data)
return ExecutionResult(data=data, errors=context.errors)

promise = Promise(executor).catch(on_rejected).then(on_resolve)
promise = Promise.resolve(None).then(executor).catch(on_rejected).then(on_resolve)
if return_promise:
return promise
context.executor.wait_until_finished()
Expand Down Expand Up @@ -218,14 +218,16 @@ def complete_value_catching_error(exe_context, return_type, field_asts, info, re
completed = complete_value(exe_context, return_type, field_asts, info, result)
if is_thenable(completed):
def handle_error(error):
exe_context.errors.append(error)
traceback = completed._traceback
exe_context.report_error(error, traceback)
return None

return completed.catch(handle_error)

return completed
except Exception as e:
exe_context.errors.append(e)
traceback = sys.exc_info()[2]
exe_context.report_error(e, traceback)
return None


Expand Down
7 changes: 6 additions & 1 deletion graphql/execution/executors/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from sys import exc_info


def process(p, f, args, kwargs):
try:
val = f(*args, **kwargs)
p.do_resolve(val)
except Exception as e:
p.do_reject(e)
traceback = exc_info()[2]
e.stack = traceback
p.do_reject(e, traceback=traceback)
2 changes: 1 addition & 1 deletion graphql/execution/tests/test_dataloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@pytest.mark.parametrize("executor", [
SyncExecutor(),
ThreadExecutor(),
# ThreadExecutor(),
])
def test_batches_correctly(executor):

Expand Down
11 changes: 6 additions & 5 deletions graphql/execution/tests/test_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def deeper(self):

def test_merges_parallel_fragments():
ast = parse('''
{ a, ...FragOne, ...FragTwo }
{ a, deep {...FragOne, ...FragTwo} }

fragment FragOne on Type {
b
Expand Down Expand Up @@ -148,14 +148,15 @@ def test_merges_parallel_fragments():
assert result.data == \
{
'a': 'Apple',
'b': 'Banana',
'c': 'Cherry',
'deep': {
'b': 'Banana',
'c': 'Cherry',
'deeper': {
'deep': {
'b': 'Banana',
'c': 'Cherry'}}
'c': 'Cherry',
'deeper': {
'b': 'Banana',
'c': 'Cherry'}}}
}


Expand Down
15 changes: 1 addition & 14 deletions graphql/execution/tests/test_resolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,7 @@
GraphQLObjectType, GraphQLSchema, GraphQLString)
from promise import Promise

class CustomPromise(object):
def __init__(self, fn=None, promise=None):
self._promise = promise or Promise(fn)

def get(self, _=None):
raise NotImplementedError("Blocking for results not allowed. Use 'then' if you want to "
"work with the result.")

def then(self, success=None, failure=None):
return self.__class__(promise=self._promise.then(success, failure))

def __getattr__(self, item):
return getattr(self._promise, item)

class CustomPromise(Promise):
@classmethod
def fulfilled(cls, x):
p = cls()
Expand Down
3 changes: 2 additions & 1 deletion graphql/type/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
is_leaf_type,
is_type,
get_nullable_type,
is_output_type
is_output_type,
Undefined
)
from .directives import (
# "Enum" of Directive locations
Expand Down
10 changes: 10 additions & 0 deletions graphql/type/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@
from ..utils.assert_valid_name import assert_valid_name


class _Undefined(object):
def __bool__(self):
return False

__nonzero__ = __bool__


Undefined = _Undefined()


def is_type(type):
return isinstance(type, (
GraphQLScalarType,
Expand Down