From 44135928d434635b51a6b6e1ef1c34857a669ddf Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 19:52:07 -0700 Subject: [PATCH 1/6] Improved Undefined object --- graphql/execution/base.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 7929cd5a..c8a42695 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -9,7 +9,14 @@ from ..utils.type_from_ast import type_from_ast from .values import get_argument_values, get_variable_values -Undefined = object() + +class _Undefined(object): + def __bool__(self): + return False + + __nonzero__ = __bool__ + +Undefined = _Undefined() class ExecutionContext(object): From a1dd11558ed11ed51dd49499407b4a28095d500b Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 21:00:39 -0700 Subject: [PATCH 2/6] Fixed CustomPromise to inherit from Promise --- graphql/execution/tests/test_resolve.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/graphql/execution/tests/test_resolve.py b/graphql/execution/tests/test_resolve.py index 42b96899..98cd09ea 100644 --- a/graphql/execution/tests/test_resolve.py +++ b/graphql/execution/tests/test_resolve.py @@ -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() From 0d7e5d36cb5dc87c17b72bb619c0643dbf661bbd Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 21:01:02 -0700 Subject: [PATCH 3/6] Improved fragment merge deep/deeper tests --- graphql/execution/tests/test_executor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 7d9b5e7f..9aff9362 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -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 @@ -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'}}} } From 8e1e1c0e0f46e745dc23bfa4f3f06b35bcafd818 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 21:01:25 -0700 Subject: [PATCH 4/6] Use only SyncExecutor for dataloader --- graphql/execution/tests/test_dataloader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphql/execution/tests/test_dataloader.py b/graphql/execution/tests/test_dataloader.py index 557b3f52..24d12957 100644 --- a/graphql/execution/tests/test_dataloader.py +++ b/graphql/execution/tests/test_dataloader.py @@ -9,7 +9,7 @@ @pytest.mark.parametrize("executor", [ SyncExecutor(), - ThreadExecutor(), + # ThreadExecutor(), ]) def test_batches_correctly(executor): From d528fad631ae45dd4b6b7d9bb77dd92718d38c4e Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 21:02:34 -0700 Subject: [PATCH 5/6] Improved exception reporting --- graphql/error/base.py | 2 +- graphql/execution/base.py | 9 ++++++++- graphql/execution/executor.py | 12 +++++++----- graphql/execution/executors/utils.py | 7 ++++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/graphql/error/base.py b/graphql/error/base.py index d3b0c894..6af9caf2 100644 --- a/graphql/error/base.py +++ b/graphql/error/base.py @@ -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 \ No newline at end of file + return self._locations diff --git a/graphql/execution/base.py b/graphql/execution/base.py index c8a42695..744820e0 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +import sys + from ..error import GraphQLError from ..language import ast from ..pyutils.default_ordered_dict import DefaultOrderedDict @@ -13,9 +15,10 @@ class _Undefined(object): def __bool__(self): return False - + __nonzero__ = __bool__ + Undefined = _Undefined() @@ -89,6 +92,10 @@ def get_argument_values(self, field_def, field_ast): return result + def report_error(self, error, traceback=None): + sys.excepthook(type(error), str(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: diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 202ba981..797c2fd7 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -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) @@ -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() @@ -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 diff --git a/graphql/execution/executors/utils.py b/graphql/execution/executors/utils.py index 4fc44875..5cad2e3c 100644 --- a/graphql/execution/executors/utils.py +++ b/graphql/execution/executors/utils.py @@ -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) From 7c956aa4fb89e6671e7934976d6ded87e59437e7 Mon Sep 17 00:00:00 2001 From: Syrus Akbary Date: Sat, 22 Jul 2017 21:45:26 -0700 Subject: [PATCH 6/6] Fixed exception printing --- graphql/execution/base.py | 14 ++------------ graphql/type/__init__.py | 3 ++- graphql/type/definition.py | 10 ++++++++++ 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/graphql/execution/base.py b/graphql/execution/base.py index 744820e0..04b3629d 100644 --- a/graphql/execution/base.py +++ b/graphql/execution/base.py @@ -4,7 +4,7 @@ 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) @@ -12,16 +12,6 @@ from .values import get_argument_values, get_variable_values -class _Undefined(object): - def __bool__(self): - return False - - __nonzero__ = __bool__ - - -Undefined = _Undefined() - - class ExecutionContext(object): """Data that must be available at all points during query execution. @@ -93,7 +83,7 @@ def get_argument_values(self, field_def, field_ast): return result def report_error(self, error, traceback=None): - sys.excepthook(type(error), str(error), getattr(error, 'stack', None) or traceback) + sys.excepthook(type(error), error, getattr(error, 'stack', None) or traceback) self.errors.append(error) def get_sub_fields(self, return_type, field_asts): diff --git a/graphql/type/__init__.py b/graphql/type/__init__.py index 153c1b5e..6f53635d 100644 --- a/graphql/type/__init__.py +++ b/graphql/type/__init__.py @@ -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 diff --git a/graphql/type/definition.py b/graphql/type/definition.py index 65e3a90e..24cee3b1 100644 --- a/graphql/type/definition.py +++ b/graphql/type/definition.py @@ -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,