Re-create UnforgivingExecutionContext compatible with graphql-core 3.1.5+

This commit is contained in:
Alex Hafner 2021-09-20 12:22:30 +01:00
parent f039af2810
commit 05ce764924
2 changed files with 132 additions and 3 deletions

View File

@ -11,6 +11,7 @@ from graphql import (
print_schema, print_schema,
subscribe, subscribe,
validate, validate,
ExecutionContext,
ExecutionResult, ExecutionResult,
GraphQLArgument, GraphQLArgument,
GraphQLBoolean, GraphQLBoolean,
@ -54,6 +55,22 @@ from .utils import get_field_as
introspection_query = get_introspection_query() introspection_query = get_introspection_query()
IntrospectionSchema = introspection_types["__Schema"] IntrospectionSchema = introspection_types["__Schema"]
class UnforgivingExecutionContext(ExecutionContext):
"""An execution context which doesn't swallow exceptions.
Instead it re-raises the original error, except for
GraphQLError, which is handled by graphql-core
"""
def handle_field_error(
self,
error: GraphQLError,
return_type,
) -> None:
if type(error.original_error) is GraphQLError:
super().handle_field_error(error, return_type)
else:
raise error.original_error
def assert_valid_root_type(type_): def assert_valid_root_type(type_):
if type_ is None: if type_ is None:

View File

@ -1,12 +1,12 @@
from graphql.type import GraphQLObjectType, GraphQLSchema from graphql.type import GraphQLObjectType, GraphQLSchema
from pytest import raises from graphql import GraphQLError
from pytest import fixture, mark, raises
from graphene.tests.utils import dedent from graphene.tests.utils import dedent
from ..field import Field from ..field import Field
from ..objecttype import ObjectType from ..objecttype import ObjectType
from ..scalars import String from ..scalars import String
from ..schema import Schema from ..schema import Schema, UnforgivingExecutionContext
class MyOtherType(ObjectType): class MyOtherType(ObjectType):
@ -68,3 +68,115 @@ def test_schema_requires_query_type():
assert len(result.errors) == 1 assert len(result.errors) == 1
error = result.errors[0] error = result.errors[0]
assert error.message == "Query root type must be provided." assert error.message == "Query root type must be provided."
class TestUnforgivingExecutionContext:
@fixture
def schema(self):
class ErrorFieldsMixin:
sanity_field = String()
expected_error_field = String()
unexpected_value_error_field = String()
unexpected_type_error_field = String()
unexpected_attribute_error_field = String()
unexpected_key_error_field = String()
@staticmethod
def resolve_sanity_field(obj, info):
return "not an error"
@staticmethod
def resolve_expected_error_field(obj, info):
raise GraphQLError("expected error")
@staticmethod
def resolve_unexpected_value_error_field(obj, info):
raise ValueError("unexpected error")
@staticmethod
def resolve_unexpected_type_error_field(obj, info):
raise TypeError("unexpected error")
@staticmethod
def resolve_unexpected_attribute_error_field(obj, info):
raise AttributeError("unexpected error")
@staticmethod
def resolve_unexpected_key_error_field(obj, info):
return {}["fails"]
class NestedObject(ErrorFieldsMixin, ObjectType):
pass
class MyQuery(ErrorFieldsMixin, ObjectType):
nested_object = Field(NestedObject)
nested_object_error = Field(NestedObject)
@staticmethod
def resolve_nested_object(obj, info):
return object()
@staticmethod
def resolve_nested_object_error(obj, info):
raise TypeError()
schema = Schema(query=MyQuery)
return schema
def test_sanity_check(self, schema):
# this should pass with no errors (sanity check)
result = schema.execute(
"query { sanityField }",
execution_context_class=UnforgivingExecutionContext,
)
assert not result.errors
assert result.data == {"sanityField": "not an error"}
def test_nested_sanity_check(self, schema):
# this should pass with no errors (sanity check)
result = schema.execute(
r"query { nestedObject { sanityField } }",
execution_context_class=UnforgivingExecutionContext,
)
assert not result.errors
assert result.data == {"nestedObject": {"sanityField": "not an error"}}
def test_graphql_error(self, schema):
result = schema.execute(
"query { expectedErrorField }",
execution_context_class=UnforgivingExecutionContext,
)
assert len(result.errors) == 1
assert result.errors[0].message == "expected error"
assert result.data == {"expectedErrorField": None}
def test_nested_graphql_error(self, schema):
result = schema.execute(
r"query { nestedObject { expectedErrorField } }",
execution_context_class=UnforgivingExecutionContext,
)
assert len(result.errors) == 1
assert result.errors[0].message == "expected error"
assert result.data == {"nestedObject": {"expectedErrorField": None}}
@mark.parametrize(
"field,exception",
[
("unexpectedValueErrorField", ValueError),
("unexpectedTypeErrorField", TypeError),
("unexpectedAttributeErrorField", AttributeError),
("unexpectedKeyErrorField", KeyError),
("nestedObject { unexpectedValueErrorField }", ValueError),
("nestedObject { unexpectedTypeErrorField }", TypeError),
("nestedObject { unexpectedAttributeErrorField }", AttributeError),
("nestedObject { unexpectedKeyErrorField }", KeyError),
("nestedObjectError { __typename }", TypeError),
],
)
def test_unexpected_error(self, field, exception, schema):
with raises(exception):
# no result, but the exception should be propagated
schema.execute(
f"query {{ {field} }}",
execution_context_class=UnforgivingExecutionContext,
)