Skip to content

Commit

Permalink
rewrite GrapQLError
Browse files Browse the repository at this point in the history
  • Loading branch information
m.kindritskiy committed Aug 8, 2024
1 parent ee4f23e commit 53f7ff9
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 26 deletions.
3 changes: 3 additions & 0 deletions docs/changelog/changes_08.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Changes in 0.8
- Change `GraphQLResponse` type - it now has both `data` and `errors` fields
- Rename `on_dispatch` hook to `on_operation`
- Remove old `on_operation` hook
- Remove `execute` method from `BaseGraphQLEndpoint` class
- Add `process_result` method to `BaseGraphQLEndpoint` class

Backward-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -29,3 +31,4 @@ Backward-incompatible changes
- Drop `hiku.federation.endpoint` - use `hiku.endpoint` instead
- Drop `hiku.federation.denormalize`
- Drop `hiku.federation.engine` - use `hiku.engine` instead
- Remove `execute` method from `BaseGraphQLEndpoint` class
8 changes: 4 additions & 4 deletions hiku/endpoint/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class GraphQLRequest(TypedDict, total=False):

class GraphQLResponse(TypedDict, total=False):
data: Optional[Dict[str, object]]
errors: Optional[List[object]]
errors: Optional[List[GraphQLErrorObject]]
extensions: Optional[Dict[str, object]]


Expand All @@ -47,8 +47,8 @@ def __init__(
def process_result(self, result: ExecutionResult) -> GraphQLResponse:
data: GraphQLResponse = {"data": result.data}

if result.error:
data["errors"] = [{"message": e} for e in result.error.errors]
if result.errors:
data["errors"] = [{"message": e.message} for e in result.errors]

return data

Expand Down Expand Up @@ -107,7 +107,7 @@ def dispatch(
) -> SingleOrBatchedResponse:
if isinstance(data, list):
if not self.batching:
raise GraphQLError(errors=["Batching is not supported"])
raise GraphQLError("Batching is not supported")

return [
super(GraphQLEndpoint, self).dispatch(item, context)
Expand Down
5 changes: 4 additions & 1 deletion hiku/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,10 @@ def __getitem__(self, item: Any) -> Any:
)


_ExecutorType = TypeVar("_ExecutorType", bound=SyncAsyncExecutor)
# Contrvariant must be used because we want to accept subclasses of Executor
_ExecutorType = TypeVar(
"_ExecutorType", covariant=True, bound=SyncAsyncExecutor
)


class Engine(Generic[_ExecutorType]):
Expand Down
2 changes: 1 addition & 1 deletion hiku/extensions/base_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def on_operation(
At this step the:
- execution_context.query_src (if type str) is set to the query string
- execution_context.query (if type Noe) is set to the query Node
- execution_context.query (if type Node) is set to the query Node
- execution_context.variables is set to the query variables
- execution_context.operation_name is set to the query operation name
- execution_context.query_graph is set to the query graph
Expand Down
32 changes: 21 additions & 11 deletions hiku/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@


class GraphQLError(Exception):
def __init__(self, *, errors: List[str]):
def __init__(self, message: str) -> None:
super().__init__(message)
self.message = message


class ValidationError(Exception):
def __init__(self, errors: List[str]) -> None:
super().__init__("{} errors".format(len(errors)))
self.errors = errors

Expand All @@ -56,7 +62,7 @@ def _run_validation(
@dataclass
class ExecutionResult:
data: Optional[Dict[str, Any]]
error: Optional[GraphQLError]
errors: Optional[List[GraphQLError]]


class Schema(Generic[_ExecutorType]):
Expand Down Expand Up @@ -158,8 +164,12 @@ def execute_sync(
).process(execution_context.query)

return ExecutionResult(data, None)
except ValidationError as e:
return ExecutionResult(
None, [GraphQLError(message) for message in e.errors]
)
except GraphQLError as e:
return ExecutionResult(None, e)
return ExecutionResult(None, [e])

async def execute(
self: "Schema[BaseAsyncExecutor]",
Expand Down Expand Up @@ -210,8 +220,12 @@ async def execute(
).process(execution_context.query)

return ExecutionResult(data, None)
except ValidationError as e:
return ExecutionResult(
None, [GraphQLError(message) for message in e.errors]
)
except GraphQLError as e:
return ExecutionResult(None, e)
return ExecutionResult(None, [e])

def _validate(
self,
Expand Down Expand Up @@ -249,11 +263,7 @@ def _init_execution_context(
execution_context.request_operation_name,
)
except TypeError as e:
raise GraphQLError(
errors=[
"Failed to read query: {}".format(e),
]
)
raise GraphQLError("Failed to read query: {}".format(e))

execution_context.query = execution_context.operation.query
# save original query before merging to validate it
Expand All @@ -266,7 +276,7 @@ def _init_execution_context(
op = execution_context.operation
if op.type not in (OperationType.QUERY, OperationType.MUTATION):
raise GraphQLError(
errors=["Unsupported operation type: {!r}".format(op.type)]
"Unsupported operation type: {!r}".format(op.type)
)

with extensions_manager.validation():
Expand All @@ -278,4 +288,4 @@ def _init_execution_context(
)

if execution_context.errors:
raise GraphQLError(errors=execution_context.errors)
raise ValidationError(errors=execution_context.errors)
2 changes: 1 addition & 1 deletion tests/extensions/test_query_depth_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ def test_query_depth_validator(sync_graph):
"""

result = schema.execute_sync(query)
assert result.error.errors == ["Query depth 4 exceeds maximum allowed depth 2"]
assert [e.message for e in result.errors] == ["Query depth 4 exceeds maximum allowed depth 2"]
14 changes: 7 additions & 7 deletions tests/test_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def test_option_not_provided_for_field():
"""

result = execute(GRAPH, read(query))
assert result.error.errors == [
assert [e.message for e in result.errors] == [
'Required option "Video.thumbnailUrl:size" is not specified'
]

Expand Down Expand Up @@ -423,7 +423,7 @@ def test_validate_interface_has_no_implementations():

result = execute(graph, read(query))

assert result.error.errors == [
assert [e.message for e in result.errors] == [
"Can not query field 'id' on interface 'Media'. "
"Interface 'Media' is not implemented by any type. "
"Add at least one type implementing this interface.",
Expand All @@ -447,7 +447,7 @@ def test_validate_query_implementation_node_field_without_inline_fragment():

result = execute(GRAPH, read(query))

assert result.error.errors == [
assert [e.message for e in result.errors] == [
"Can not query field 'album' on type 'Media'. "
"Did you mean to use an inline fragment on 'Audio'?"
]
Expand All @@ -465,7 +465,7 @@ def test_validate_query_fragment_no_type_condition():

result = execute(GRAPH, read(query, {'text': 'foo'}))

assert result.error.errors == [
assert [e.message for e in result.errors] == [
"Can not query field 'album' on type 'Media'. "
"Did you mean to use an inline fragment on 'Audio'?"
]
Expand All @@ -484,7 +484,7 @@ def test_validate_query_fragment_on_unknown_type():

result = execute(GRAPH, read(query, {'text': 'foo'}))

assert result.error.errors == ["Fragment on unknown type 'X'"]
assert [e.message for e in result.errors] == ["Fragment on unknown type 'X'"]


def test_validate_interface_type_has_no_such_field():
Expand All @@ -504,7 +504,7 @@ def test_validate_interface_type_has_no_such_field():

result = execute(GRAPH, read(query, {'text': 'foo'}))

assert result.error.errors == [
assert [e.message for e in result.errors] == [
'Field "invalid_field" is not implemented in the "Audio" node',
]

Expand All @@ -525,6 +525,6 @@ def test_validate_interface_type_field_has_no_such_option():

result = execute(GRAPH, read(query, {'text': 'foo'}))

assert result.error.errors == [
assert [e.message for e in result.errors] == [
'Unknown options for "Audio.duration": size',
]
2 changes: 1 addition & 1 deletion tests/test_union.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ def test_option_not_provided_for_field():
}
"""
result = execute(read(query))
result.error.errors == [
result.errors == [
"Required option \"size\" for Field('thumbnailUrl'"
]

Expand Down

0 comments on commit 53f7ff9

Please sign in to comment.