diff --git a/graphql/error/tests/test_base.py b/graphql/error/tests/test_base.py index fa695282..38c71b81 100644 --- a/graphql/error/tests/test_base.py +++ b/graphql/error/tests/test_base.py @@ -5,6 +5,7 @@ from promise import Promise +from graphql.error import GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString @@ -22,7 +23,7 @@ def test_raise(): def resolver(context, *_): # type: (Optional[Any], *ResolveInfo) -> None - raise Exception("Failed") + raise GraphQLError("Failed") Type = GraphQLObjectType( "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} @@ -38,14 +39,14 @@ def test_reraise(): def resolver(context, *_): # type: (Optional[Any], *ResolveInfo) -> None - raise Exception("Failed") + raise GraphQLError("Failed") Type = GraphQLObjectType( "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} ) result = execute(GraphQLSchema(Type), ast) - with pytest.raises(Exception) as exc_info: + with pytest.raises(GraphQLError) as exc_info: result.errors[0].reraise() extracted = traceback.extract_tb(exc_info.tb) @@ -59,7 +60,7 @@ def resolver(context, *_): "return executor.execute(resolve_fn, source, info, **args)", ), ("execute", "return fn(*args, **kwargs)"), - ("resolver", 'raise Exception("Failed")'), + ("resolver", 'raise GraphQLError("Failed")'), ] assert str(exc_info.value) == "Failed" @@ -71,7 +72,7 @@ def test_reraise_from_promise(): ast = parse("query Example { a }") def fail(): - raise Exception("Failed") + raise GraphQLError("Failed") def resolver(context, *_): # type: (Optional[Any], *ResolveInfo) -> None @@ -93,7 +94,7 @@ def resolver(context, *_): ("test_reraise_from_promise", "result.errors[0].reraise()"), ("_resolve_from_executor", "executor(resolve, reject)"), ("", "return Promise(lambda resolve, reject: resolve(fail()))"), - ("fail", 'raise Exception("Failed")'), + ("fail", 'raise GraphQLError("Failed")'), ] assert str(exc_info.value) == "Failed" diff --git a/graphql/execution/executor.py b/graphql/execution/executor.py index 472f95b9..405478de 100644 --- a/graphql/execution/executor.py +++ b/graphql/execution/executor.py @@ -128,8 +128,10 @@ def promise_executor(v): def on_rejected(error): # type: (Exception) -> None - exe_context.errors.append(error) - return None + if isinstance(error, GraphQLError): + exe_context.errors.append(error) + return None + return Promise.rejected(error) def on_resolve(data): # type: (Union[None, Dict, Observable]) -> Union[ExecutionResult, Observable] @@ -272,9 +274,6 @@ def subscribe_fields( # type: (...) -> Observable subscriber_exe_context = SubscriberExecutionContext(exe_context) - def on_error(error): - subscriber_exe_context.report_error(error) - def map_result(data): # type: (Dict[str, Any]) -> ExecutionResult if subscriber_exe_context.errors: @@ -451,11 +450,12 @@ def resolve_or_error( try: return executor.execute(resolve_fn, source, info, **args) except Exception as e: - logger.exception( - "An error occurred while resolving field {}.{}".format( - info.parent_type.name, info.field_name + if not isinstance(e, GraphQLError): + logger.exception( + "An error occurred while resolving field {}.{}".format( + info.parent_type.name, info.field_name + ) ) - ) e.stack = sys.exc_info()[2] # type: ignore return e @@ -484,6 +484,8 @@ def complete_value_catching_error( def handle_error(error): # type: (Union[GraphQLError, GraphQLLocatedError]) -> Optional[Any] + if not isinstance(error, GraphQLError): + return Promise.rejected(error) traceback = completed._traceback # type: ignore exe_context.report_error(error, traceback) return None @@ -491,7 +493,7 @@ def handle_error(error): return completed.catch(handle_error) return completed - except Exception as e: + except GraphQLError as e: traceback = sys.exc_info()[2] exe_context.report_error(e, traceback) return None @@ -535,12 +537,16 @@ def complete_value( GraphQLLocatedError( # type: ignore field_asts, original_error=error, path=path ) + if isinstance(error, GraphQLError) + else error ), ) # print return_type, type(result) - if isinstance(result, Exception): + if isinstance(result, GraphQLError): raise GraphQLLocatedError(field_asts, original_error=result, path=path) + if isinstance(result, Exception): + raise result if isinstance(return_type, GraphQLNonNull): return complete_nonnull_value( diff --git a/graphql/execution/tests/test_executor.py b/graphql/execution/tests/test_executor.py index 69b64e45..7cbac7e8 100644 --- a/graphql/execution/tests/test_executor.py +++ b/graphql/execution/tests/test_executor.py @@ -264,7 +264,7 @@ def ok(self): def error(self): # type: () -> NoReturn - raise Exception("Error getting error") + raise GraphQLError("Error getting error") doc_ast = parse(doc) @@ -567,10 +567,8 @@ def test_fails_to_execute_a_query_containing_a_type_definition(): assert isinstance(nodes[0], ObjectTypeDefinition) -def test_exceptions_are_reraised_if_specified(mocker): - # type: (MockFixture) -> None - - logger = mocker.patch("graphql.execution.executor.logger") +def test_exceptions_are_reraised(): + # type: () -> None query = parse( """ @@ -578,9 +576,12 @@ def test_exceptions_are_reraised_if_specified(mocker): """ ) + class Error(Exception): + pass + def resolver(*_): # type: (*Any) -> NoReturn - raise Exception("UH OH!") + raise Error("UH OH!") schema = GraphQLSchema( GraphQLObjectType( @@ -588,11 +589,36 @@ def resolver(*_): ) ) - execute(schema, query) - logger.exception.assert_called_with( - "An error occurred while resolving field Query.foo" + with raises(Error): + execute(schema, query) + + +def test_exceptions_are_reraised_promise(): + # type: () -> None + + query = parse( + """ + { foo } + """ ) + class Error(Exception): + pass + + @Promise.promisify + def resolver(*_): + # type: (*Any) -> NoReturn + raise Error("UH OH!") + + schema = GraphQLSchema( + GraphQLObjectType( + name="Query", fields={"foo": GraphQLField(GraphQLString, resolver=resolver)} + ) + ) + + with raises(Error): + execute(schema, query) + def test_executor_properly_propogates_path_data(mocker): # type: (MockFixture) -> None diff --git a/graphql/execution/tests/test_executor_asyncio.py b/graphql/execution/tests/test_executor_asyncio.py index 714f59ee..cbd9d937 100644 --- a/graphql/execution/tests/test_executor_asyncio.py +++ b/graphql/execution/tests/test_executor_asyncio.py @@ -8,7 +8,7 @@ asyncio = pytest.importorskip("asyncio") -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import GraphQLField, GraphQLObjectType, GraphQLSchema, GraphQLString @@ -95,7 +95,7 @@ def resolver(context, *_): def resolver_2(context, *_): # type: (Optional[Any], *ResolveInfo) -> NoReturn asyncio.sleep(0.003) - raise Exception("resolver_2 failed!") + raise GraphQLError("resolver_2 failed!") Type = GraphQLObjectType( "Type", @@ -117,6 +117,25 @@ def resolver_2(context, *_): assert result.data == {"a": "hey", "b": None} +def test_asyncio_executor_exceptions_reraised(): + # type: () -> None + ast = parse("query Example { a }") + + class Error(Exception): + pass + + def resolver(context, *_): + # type: (Optional[Any], *ResolveInfo) -> str + raise Error("UH OH!") + + Type = GraphQLObjectType( + "Type", {"a": GraphQLField(GraphQLString, resolver=resolver)} + ) + + with pytest.raises(Error): + execute(GraphQLSchema(Type), ast, executor=AsyncioExecutor()) + + def test_evaluates_mutations_serially(): # type: () -> None assert_evaluate_mutations_serially(executor=AsyncioExecutor()) diff --git a/graphql/execution/tests/test_executor_gevent.py b/graphql/execution/tests/test_executor_gevent.py index 9256a018..e5e2ed2d 100644 --- a/graphql/execution/tests/test_executor_gevent.py +++ b/graphql/execution/tests/test_executor_gevent.py @@ -8,7 +8,7 @@ gevent = pytest.importorskip("gevent") -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.location import SourceLocation from graphql.language.parser import parse @@ -54,7 +54,7 @@ def resolver(context, *_): def resolver_2(context, *_): gevent.sleep(0.003) - raise Exception("resolver_2 failed!") + raise GraphQLError("resolver_2 failed!") Type = GraphQLObjectType( "Type", diff --git a/graphql/execution/tests/test_executor_thread.py b/graphql/execution/tests/test_executor_thread.py index 0c06bbe9..a9db33e2 100644 --- a/graphql/execution/tests/test_executor_thread.py +++ b/graphql/execution/tests/test_executor_thread.py @@ -1,5 +1,5 @@ # type: ignore -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import ( @@ -191,19 +191,19 @@ def sync(self): def syncError(self): # type: () -> NoReturn - raise Exception("Error getting syncError") + raise GraphQLError("Error getting syncError") def syncReturnError(self): # type: () -> Exception - return Exception("Error getting syncReturnError") + return GraphQLError("Error getting syncReturnError") def syncReturnErrorList(self): # type: () -> List[Union[Exception, str]] return [ "sync0", - Exception("Error getting syncReturnErrorList1"), + GraphQLError("Error getting syncReturnErrorList1"), "sync2", - Exception("Error getting syncReturnErrorList3"), + GraphQLError("Error getting syncReturnErrorList3"), ] def asyncBasic(self): @@ -212,15 +212,15 @@ def asyncBasic(self): def asyncReject(self): # type: () -> Promise - return rejected(Exception("Error getting asyncReject")) + return rejected(GraphQLError("Error getting asyncReject")) def asyncEmptyReject(self): # type: () -> Promise - return rejected(Exception("An unknown error occurred.")) + return rejected(GraphQLError("An unknown error occurred.")) def asyncReturnError(self): # type: () -> Promise - return resolved(Exception("Error getting asyncReturnError")) + return resolved(GraphQLError("Error getting asyncReturnError")) schema = GraphQLSchema( query=GraphQLObjectType( diff --git a/graphql/execution/tests/test_lists.py b/graphql/execution/tests/test_lists.py index 3534a137..ecf761a7 100644 --- a/graphql/execution/tests/test_lists.py +++ b/graphql/execution/tests/test_lists.py @@ -1,7 +1,7 @@ # type: ignore from collections import namedtuple -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import ( @@ -66,7 +66,7 @@ class Test_ListOfT_Promise_Array_T: # [T] Promise> ) test_returns_null = check(resolved(None), {"data": {"nest": {"test": None}}}) test_rejected = check( - lambda: rejected(Exception("bad")), + lambda: rejected(GraphQLError("bad")), { "data": {"nest": {"test": None}}, "errors": [ @@ -91,7 +91,7 @@ class Test_ListOfT_Array_Promise_T: # [T] Array> {"data": {"nest": {"test": [1, None, 2]}}}, ) test_contains_reject = check( - lambda: [resolved(1), rejected(Exception("bad")), resolved(2)], + lambda: [resolved(1), rejected(GraphQLError("bad")), resolved(2)], { "data": {"nest": {"test": [1, None, 2]}}, "errors": [ @@ -149,7 +149,7 @@ class Test_NotNullListOfT_Promise_Array_T: # [T]! Promise>> ) test_rejected = check( - lambda: rejected(Exception("bad")), + lambda: rejected(GraphQLError("bad")), { "data": {"nest": None}, "errors": [ @@ -173,7 +173,7 @@ class Test_NotNullListOfT_Array_Promise_T: # [T]! Promise>> {"data": {"nest": {"test": [1, None, 2]}}}, ) test_contains_reject = check( - lambda: [resolved(1), rejected(Exception("bad")), resolved(2)], + lambda: [resolved(1), rejected(GraphQLError("bad")), resolved(2)], { "data": {"nest": {"test": [1, None, 2]}}, "errors": [ @@ -228,7 +228,7 @@ class TestListOfNotNullT_Promise_Array_T: # [T!] Promise> test_returns_null = check(resolved(None), {"data": {"nest": {"test": None}}}) test_rejected = check( - lambda: rejected(Exception("bad")), + lambda: rejected(GraphQLError("bad")), { "data": {"nest": {"test": None}}, "errors": [ @@ -262,7 +262,7 @@ class TestListOfNotNullT_Array_Promise_T: # [T!] Array> }, ) test_contains_reject = check( - lambda: [resolved(1), rejected(Exception("bad")), resolved(2)], + lambda: [resolved(1), rejected(GraphQLError("bad")), resolved(2)], { "data": {"nest": {"test": None}}, "errors": [ @@ -341,7 +341,7 @@ class TestNotNullListOfNotNullT_Promise_Array_T: # [T!]! Promise> ) test_rejected = check( - lambda: rejected(Exception("bad")), + lambda: rejected(GraphQLError("bad")), { "data": {"nest": None}, "errors": [ @@ -375,7 +375,7 @@ class TestNotNullListOfNotNullT_Array_Promise_T: # [T!]! Array> }, ) test_contains_reject = check( - lambda: [resolved(1), rejected(Exception("bad")), resolved(2)], + lambda: [resolved(1), rejected(GraphQLError("bad")), resolved(2)], { "data": {"nest": None}, "errors": [ diff --git a/graphql/execution/tests/test_located_error.py b/graphql/execution/tests/test_located_error.py index b5bec97f..6ec641f9 100644 --- a/graphql/execution/tests/test_located_error.py +++ b/graphql/execution/tests/test_located_error.py @@ -9,7 +9,7 @@ execute, parse, ) -from graphql.error import GraphQLLocatedError +from graphql.error import GraphQLError, GraphQLLocatedError def test_unicode_error_message(): @@ -18,7 +18,7 @@ def test_unicode_error_message(): def resolver(context, *_): # type: (Optional[Any], *ResolveInfo) -> NoReturn - raise Exception(u"UNIÇODÉ!") + raise GraphQLError(u"UNIÇODÉ!") Type = GraphQLObjectType( "Type", {"unicode": GraphQLField(GraphQLString, resolver=resolver)} diff --git a/graphql/execution/tests/test_mutations.py b/graphql/execution/tests/test_mutations.py index d7ec17bc..f47dd26e 100644 --- a/graphql/execution/tests/test_mutations.py +++ b/graphql/execution/tests/test_mutations.py @@ -1,4 +1,5 @@ # type: ignore +from graphql.error import GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import ( @@ -39,7 +40,7 @@ def promise_to_change_the_number(self, n): def fail_to_change_the_number(self, n): # type: (int) -> None - raise Exception("Cannot change the number") + raise GraphQLError("Cannot change the number") def promise_and_fail_to_change_the_number(self, n): # type: (int) -> None diff --git a/graphql/execution/tests/test_nonnull.py b/graphql/execution/tests/test_nonnull.py index 6f8a3725..a8b36022 100644 --- a/graphql/execution/tests/test_nonnull.py +++ b/graphql/execution/tests/test_nonnull.py @@ -1,5 +1,5 @@ # type: ignore -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.type import ( @@ -17,10 +17,10 @@ from promise import Promise from typing import Any, Optional, Dict, Tuple, Union -sync_error = Exception("sync") -non_null_sync_error = Exception("nonNullSync") -promise_error = Exception("promise") -non_null_promise_error = Exception("nonNullPromise") +sync_error = GraphQLError("sync") +non_null_sync_error = GraphQLError("nonNullSync") +promise_error = GraphQLError("promise") +non_null_promise_error = GraphQLError("nonNullPromise") class ThrowingData(object): diff --git a/graphql/execution/tests/test_subscribe.py b/graphql/execution/tests/test_subscribe.py index da458832..a53cd6ed 100644 --- a/graphql/execution/tests/test_subscribe.py +++ b/graphql/execution/tests/test_subscribe.py @@ -13,6 +13,7 @@ graphql, subscribe, ) +from graphql.error import GraphQLError # Necessary for static type checking if False: # flake8: noqa @@ -270,7 +271,7 @@ def test_throws_an_error_if_subscribe_does_not_return_an_iterator(): def test_returns_an_error_if_subscribe_function_returns_error(): # type: () -> None - exc = Exception("Throw!") + exc = GraphQLError("Throw!") def thrower(root, info): # type: (Optional[Any], ResolveInfo) -> None diff --git a/graphql/execution/utils.py b/graphql/execution/utils.py index 6e71ea6b..4ac3a0d5 100644 --- a/graphql/execution/utils.py +++ b/graphql/execution/utils.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- import logging -from traceback import format_exception from ..error import GraphQLError from ..language import ast @@ -148,11 +147,7 @@ def get_argument_values(self, field_def, field_ast): return self.argument_values_cache[k] def report_error(self, error, traceback=None): - # type: (Exception, Optional[TracebackType]) -> None - exception = format_exception( - type(error), error, getattr(error, "stack", None) or traceback - ) - logger.error("".join(exception)) + # type: (GraphQLError, Optional[TracebackType]) -> None self.errors.append(error) def get_sub_fields(self, return_type, field_asts): diff --git a/graphql/graphql.py b/graphql/graphql.py index 6793cf0e..ce82eaa0 100644 --- a/graphql/graphql.py +++ b/graphql/graphql.py @@ -1,4 +1,5 @@ from .execution import ExecutionResult +from .error import GraphQLError from .backend import get_default_backend from promise import promisify @@ -69,7 +70,7 @@ def execute_graphql( middleware=middleware, **execute_options ) - except Exception as e: + except GraphQLError as e: return ExecutionResult(errors=[e], invalid=True) diff --git a/graphql/utils/build_client_schema.py b/graphql/utils/build_client_schema.py index 152abaaf..2af3be00 100644 --- a/graphql/utils/build_client_schema.py +++ b/graphql/utils/build_client_schema.py @@ -34,6 +34,7 @@ __Type, __TypeKind, ) +from ..error import GraphQLError from .value_from_ast import value_from_ast @@ -46,7 +47,7 @@ def _none(*_): def no_execution(root, info, **args): - raise Exception("Client Schema cannot be used for execution.") + raise GraphQLError("Client Schema cannot be used for execution.") def build_client_schema(introspection): @@ -77,14 +78,14 @@ def get_type(type_ref): item_ref = type_ref.get("ofType") if not item_ref: - raise Exception("Decorated type deeper than introspection query.") + raise GraphQLError("Decorated type deeper than introspection query.") return GraphQLList(get_type(item_ref)) elif kind == TypeKind.NON_NULL: nullable_ref = type_ref.get("ofType") if not nullable_ref: - raise Exception("Decorated type deeper than introspection query.") + raise GraphQLError("Decorated type deeper than introspection query.") return GraphQLNonNull(get_type(nullable_ref)) @@ -96,7 +97,7 @@ def get_named_type(type_name): type_introspection = type_introspection_map.get(type_name) if not type_introspection: - raise Exception( + raise GraphQLError( "Invalid or incomplete schema, unknown type: {}. Ensure that a full introspection query " "is used in order to build a client schema.".format(type_name) ) @@ -136,7 +137,7 @@ def build_type(type): type_kind = type.get("kind") handler = type_builders.get(type_kind) if not handler: - raise Exception( + raise GraphQLError( "Invalid or incomplete schema, unknown kind: {}. Ensure that a full introspection query " "is used in order to build a client schema.".format(type_kind) ) diff --git a/graphql/utils/extend_schema.py b/graphql/utils/extend_schema.py index 7f4868db..b33d5fee 100644 --- a/graphql/utils/extend_schema.py +++ b/graphql/utils/extend_schema.py @@ -386,4 +386,4 @@ def build_field_type(type_ast): def cannot_execute_client_schema(*args, **kwargs): - raise Exception("Client Schema cannot be used for execution.") + raise GraphQLError("Client Schema cannot be used for execution.") diff --git a/tests_py35/core_execution/test_asyncio_executor.py b/tests_py35/core_execution/test_asyncio_executor.py index 2bfff7c5..fdd3cffb 100644 --- a/tests_py35/core_execution/test_asyncio_executor.py +++ b/tests_py35/core_execution/test_asyncio_executor.py @@ -1,6 +1,6 @@ import asyncio -from graphql.error import format_error +from graphql.error import format_error, GraphQLError from graphql.execution import execute from graphql.language.parser import parse from graphql.execution.executors.asyncio import AsyncioExecutor @@ -82,7 +82,7 @@ async def resolver(context, *_): async def resolver_2(context, *_): await asyncio.sleep(0.003) - raise Exception("resolver_2 failed!") + raise GraphQLError("resolver_2 failed!") Type = GraphQLObjectType( "Type",