Skip to content

Commit

Permalink
rewrite GrapQLError
Browse files Browse the repository at this point in the history
  • Loading branch information
m.kindritskiy committed Sep 14, 2024
1 parent 8d19c91 commit d43833d
Show file tree
Hide file tree
Showing 15 changed files with 223 additions and 160 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[flake8]
max-line-length = 80
exclude = *_pb2.py,.tox,.git,env,docs,.venv,__pypackages__,tests
extend-ignore = E203
extend-ignore = E203,E231
ignore =
F811,
W503,
9 changes: 9 additions & 0 deletions docs/changelog/changes_08.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ 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
- Move `GraphQLError` to `hiku.error` module
- Drop `GraphQLError.errors` field. Earlier we used to store multiple errors in single `GraphQLError` but now its one message - one `GraphQLError`.
- Add `GraphQLError.message` field

Backward-incompatible changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -29,3 +34,7 @@ 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
- Move `GraphQLError` to `hiku.error` module
- Drop `GraphQLError.errors` field
- Add `GraphQLError.message` field
8 changes: 7 additions & 1 deletion hiku/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(

def __getattr__(self, name):
assert self.__field_name__ is None, self.__field_name__
return self.__class__(name)
return self.__class__(name, mutation=self.__mutation__)

def __getitem__(self, items):
assert self.__node_items__ is None, self.__node_items__
Expand All @@ -36,13 +36,19 @@ def __lshift__(self, other):
other.__field_options__,
self.__field_name__, # alias
other.__node_items__,
other.__mutation__,
)

def __call__(self, **options):
assert self.__field_options__ is None, self.__field_options__
self.__field_options__ = options
return self

def __repr__(self) -> str:
return "Handle[{}]({})".format(
"Q" if not self.__mutation__ else "M", id(self)
)


# Q for query
Q = Handle()
Expand Down
8 changes: 4 additions & 4 deletions hiku/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ def create_execution_context(
elif isinstance(query, Node):
query_node = query
if "operation" not in kwargs:
kwargs["operation"] = Operation(
OperationType.QUERY,
query,
)
op_type = OperationType.QUERY
if query.ordered:
op_type = OperationType.MUTATION
kwargs["operation"] = Operation(op_type, query)

return ExecutionContext(
query_src=query_src or "",
Expand Down
12 changes: 6 additions & 6 deletions hiku/endpoint/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from abc import ABC
from asyncio import gather


from hiku.schema import ExecutionResult, GraphQLError, Schema
from hiku.error import GraphQLError
from hiku.schema import ExecutionResult, Schema


class GraphQLErrorObject(TypedDict):
Expand All @@ -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 @@ -1005,7 +1005,10 @@ def __getitem__(self, item: Any) -> Any:
)


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


class Engine(Generic[_ExecutorType]):
Expand Down
7 changes: 7 additions & 0 deletions hiku/error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
__all__ = ["GraphQLError"]


class GraphQLError(Exception):
def __init__(self, message: str) -> None:
super().__init__(message)
self.message = message
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
15 changes: 8 additions & 7 deletions hiku/extensions/query_parse_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ def __init__(self, maxsize: Optional[int] = None):
self.cached_parser = lru_cache(maxsize=maxsize)(parse_query)

def on_parse(self, execution_context: ExecutionContext) -> Iterator[None]:
execution_context.graphql_document = self.cached_parser(
execution_context.query_src,
)

info = self.cached_parser.cache_info()
QUERY_CACHE_HITS.set(info.hits)
QUERY_CACHE_MISSES.set(info.misses)
if execution_context.query_src:
execution_context.graphql_document = self.cached_parser(
execution_context.query_src,
)

info = self.cached_parser.cache_info()
QUERY_CACHE_HITS.set(info.hits)
QUERY_CACHE_MISSES.set(info.misses)
yield
1 change: 0 additions & 1 deletion hiku/federation/sdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,6 @@ def skip(field: t.Union[Field, Link]) -> bool:


def print_sdl(
# TODO: accept schema ???
graph: FederationGraph,
mutation_graph: Optional[Graph] = None,
federation_version: int = DEFAULT_FEDERATION_VERSION,
Expand Down
30 changes: 17 additions & 13 deletions hiku/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from hiku.denormalize.graphql import DenormalizeGraphQL
from hiku.engine import _ExecutorType, Engine
from hiku.error import GraphQLError
from hiku.executors.base import (
BaseAsyncExecutor,
BaseSyncExecutor,
Expand All @@ -35,8 +36,8 @@
from hiku.validate.query import validate


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

Expand All @@ -56,7 +57,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 +159,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 @@ -199,7 +204,6 @@ async def execute(
)

with extensions_manager.execution():
# result = self.engine.execute(execution_context)
result = await self.engine.execute(execution_context)
execution_context.result = result

Expand All @@ -210,8 +214,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 +257,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 +270,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 +282,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"]
Loading

0 comments on commit d43833d

Please sign in to comment.