From fe5d16027243b4df6f896d145eac7dbcf04481fc Mon Sep 17 00:00:00 2001 From: Luke Taverne Date: Mon, 16 May 2022 09:30:41 +0200 Subject: [PATCH 1/6] Check for errors during client.fetch_schema() --- gql/client.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gql/client.py b/gql/client.py index c0972133..723788d0 100644 --- a/gql/client.py +++ b/gql/client.py @@ -802,6 +802,15 @@ def fetch_schema(self) -> None: Don't use this function and instead set the fetch_schema_from_transport attribute to True""" execution_result = self.transport.execute(parse(get_introspection_query())) + + if execution_result.errors: + raise TransportQueryError( + str(execution_result.errors[0]), + errors=execution_result.errors, + data=execution_result.data, + extensions=execution_result.extensions, + ) + self.client.introspection = execution_result.data self.client.schema = build_client_schema(self.client.introspection) @@ -1175,6 +1184,15 @@ async def fetch_schema(self) -> None: execution_result = await self.transport.execute( parse(get_introspection_query()) ) + + if execution_result.errors: + raise TransportQueryError( + str(execution_result.errors[0]), + errors=execution_result.errors, + data=execution_result.data, + extensions=execution_result.extensions, + ) + self.client.introspection = execution_result.data self.client.schema = build_client_schema(self.client.introspection) From 175b569a7b6eb5bcd288981648b5ab9f94f362d5 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Thu, 19 May 2022 18:36:03 +0200 Subject: [PATCH 2/6] Modify message in Exception --- gql/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gql/client.py b/gql/client.py index 723788d0..ed84d121 100644 --- a/gql/client.py +++ b/gql/client.py @@ -805,7 +805,7 @@ def fetch_schema(self) -> None: if execution_result.errors: raise TransportQueryError( - str(execution_result.errors[0]), + f"Error while fetching schema: {execution_result.errors[0]!s}", errors=execution_result.errors, data=execution_result.data, extensions=execution_result.extensions, @@ -1187,7 +1187,7 @@ async def fetch_schema(self) -> None: if execution_result.errors: raise TransportQueryError( - str(execution_result.errors[0]), + f"Error while fetching schema: {execution_result.errors[0]!s}", errors=execution_result.errors, data=execution_result.data, extensions=execution_result.extensions, From affbfa761eb61d1d9d2979cda36f0ce1c2356756 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Thu, 19 May 2022 18:57:14 +0200 Subject: [PATCH 3/6] Add tests --- tests/test_aiohttp.py | 43 ++++++++++++++++++++++++++++++++++++ tests/test_requests.py | 49 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/tests/test_aiohttp.py b/tests/test_aiohttp.py index 2535ddb3..a5a3127d 100644 --- a/tests/test_aiohttp.py +++ b/tests/test_aiohttp.py @@ -1190,3 +1190,46 @@ async def handler(request): africa = continents[0] assert africa["code"] == "AF" + + +@pytest.mark.asyncio +async def test_aiohttp_error_fetching_schema(event_loop, aiohttp_server): + from aiohttp import web + from gql.transport.aiohttp import AIOHTTPTransport + + error_answer = """ +{ + "errors": [ + { + "errorType": "UnauthorizedException", + "message": "Permission denied" + } + ] +} +""" + + async def handler(request): + return web.Response( + text=error_answer, + content_type="application/json", + ) + + app = web.Application() + app.router.add_route("POST", "/", handler) + server = await aiohttp_server(app) + + url = server.make_url("/") + + transport = AIOHTTPTransport(url=url, timeout=10) + + with pytest.raises(TransportQueryError) as exc_info: + async with Client(transport=transport, fetch_schema_from_transport=True): + pass + + expected_error = ( + "Error while fetching schema: " + "{'errorType': 'UnauthorizedException', 'message': 'Permission denied'}" + ) + + assert expected_error in str(exc_info.value) + assert transport.session is None diff --git a/tests/test_requests.py b/tests/test_requests.py index 7cd7f712..70fc337e 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -755,3 +755,52 @@ def test_code(): f2.close() await run_sync_test(event_loop, server, test_code) + + +@pytest.mark.aiohttp +@pytest.mark.asyncio +async def test_requests_error_fetching_schema( + event_loop, aiohttp_server, run_sync_test +): + from aiohttp import web + from gql.transport.requests import RequestsHTTPTransport + + error_answer = """ +{ + "errors": [ + { + "errorType": "UnauthorizedException", + "message": "Permission denied" + } + ] +} +""" + + async def handler(request): + return web.Response( + text=error_answer, + content_type="application/json", + ) + + app = web.Application() + app.router.add_route("POST", "/", handler) + server = await aiohttp_server(app) + + url = server.make_url("/") + + def test_code(): + transport = RequestsHTTPTransport(url=url) + + with pytest.raises(TransportQueryError) as exc_info: + with Client(transport=transport, fetch_schema_from_transport=True): + pass + + expected_error = ( + "Error while fetching schema: " + "{'errorType': 'UnauthorizedException', 'message': 'Permission denied'}" + ) + + assert expected_error in str(exc_info.value) + assert transport.session is None + + await run_sync_test(event_loop, server, test_code) From 6336d1de8f7b43bab0afc44cc0fd46030cd6e7ce Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Thu, 19 May 2022 21:19:17 +0200 Subject: [PATCH 4/6] Add some docs for TransportQueryError --- docs/advanced/error_handling.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/advanced/error_handling.rst b/docs/advanced/error_handling.rst index 2fd1e39b..4e6618c9 100644 --- a/docs/advanced/error_handling.rst +++ b/docs/advanced/error_handling.rst @@ -41,6 +41,11 @@ Here are the possible Transport Errors: The message of the exception contains the first error returned by the backend. All the errors messages are available in the exception :code:`errors` attribute. + If the error message begins with :code:`Error while fetching schema:`, it means + that gql was not able to get the schema from the backend. + If you don't need the schema, you can try to create the client with + :code:`fetch_schema_from_transport=False` + - :class:`TransportClosed `: This exception is generated when the client is trying to use the transport while the transport was previously closed. From 304fb72f0aefed881afea0245bef8101e50c48b9 Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Thu, 19 May 2022 21:32:48 +0200 Subject: [PATCH 5/6] Propose fetch_schema_from_transport=False in Exception message --- gql/client.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gql/client.py b/gql/client.py index ed84d121..09282cf6 100644 --- a/gql/client.py +++ b/gql/client.py @@ -805,7 +805,11 @@ def fetch_schema(self) -> None: if execution_result.errors: raise TransportQueryError( - f"Error while fetching schema: {execution_result.errors[0]!s}", + ( + f"Error while fetching schema: {execution_result.errors[0]!s}\n" + "If you don't need the schema, you can try with: " + '"fetch_schema_from_transport=False"' + ), errors=execution_result.errors, data=execution_result.data, extensions=execution_result.extensions, @@ -1187,7 +1191,11 @@ async def fetch_schema(self) -> None: if execution_result.errors: raise TransportQueryError( - f"Error while fetching schema: {execution_result.errors[0]!s}", + ( + f"Error while fetching schema: {execution_result.errors[0]!s}\n" + "If you don't need the schema, you can try with: " + '"fetch_schema_from_transport=False"' + ), errors=execution_result.errors, data=execution_result.data, extensions=execution_result.extensions, From 64ac830cdbac6c86dec0fe366167bf3bbbb82e2d Mon Sep 17 00:00:00 2001 From: Hanusz Leszek Date: Thu, 19 May 2022 21:49:23 +0200 Subject: [PATCH 6/6] Making code more DRY --- gql/client.py | 53 +++++++++++++++++++++------------------------------ 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/gql/client.py b/gql/client.py index 09282cf6..fdac4a36 100644 --- a/gql/client.py +++ b/gql/client.py @@ -1,12 +1,13 @@ import asyncio import sys import warnings -from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union, overload +from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union, cast, overload from graphql import ( DocumentNode, ExecutionResult, GraphQLSchema, + IntrospectionQuery, build_ast_schema, get_introspection_query, parse, @@ -55,7 +56,7 @@ class Client: def __init__( self, schema: Optional[Union[str, GraphQLSchema]] = None, - introspection=None, + introspection: Optional[IntrospectionQuery] = None, transport: Optional[Union[Transport, AsyncTransport]] = None, fetch_schema_from_transport: bool = False, execute_timeout: Optional[Union[int, float]] = 10, @@ -106,7 +107,7 @@ def __init__( self.schema: Optional[GraphQLSchema] = schema # Answer of the introspection query - self.introspection = introspection + self.introspection: Optional[IntrospectionQuery] = introspection # GraphQL transport chosen self.transport: Optional[Union[Transport, AsyncTransport]] = transport @@ -131,6 +132,22 @@ def validate(self, document: DocumentNode): if validation_errors: raise validation_errors[0] + def _build_schema_from_introspection(self, execution_result: ExecutionResult): + if execution_result.errors: + raise TransportQueryError( + ( + f"Error while fetching schema: {execution_result.errors[0]!s}\n" + "If you don't need the schema, you can try with: " + '"fetch_schema_from_transport=False"' + ), + errors=execution_result.errors, + data=execution_result.data, + extensions=execution_result.extensions, + ) + + self.introspection = cast(IntrospectionQuery, execution_result.data) + self.schema = build_client_schema(self.introspection) + @overload def execute_sync( self, @@ -803,20 +820,7 @@ def fetch_schema(self) -> None: attribute to True""" execution_result = self.transport.execute(parse(get_introspection_query())) - if execution_result.errors: - raise TransportQueryError( - ( - f"Error while fetching schema: {execution_result.errors[0]!s}\n" - "If you don't need the schema, you can try with: " - '"fetch_schema_from_transport=False"' - ), - errors=execution_result.errors, - data=execution_result.data, - extensions=execution_result.extensions, - ) - - self.client.introspection = execution_result.data - self.client.schema = build_client_schema(self.client.introspection) + self.client._build_schema_from_introspection(execution_result) @property def transport(self): @@ -1189,20 +1193,7 @@ async def fetch_schema(self) -> None: parse(get_introspection_query()) ) - if execution_result.errors: - raise TransportQueryError( - ( - f"Error while fetching schema: {execution_result.errors[0]!s}\n" - "If you don't need the schema, you can try with: " - '"fetch_schema_from_transport=False"' - ), - errors=execution_result.errors, - data=execution_result.data, - extensions=execution_result.extensions, - ) - - self.client.introspection = execution_result.data - self.client.schema = build_client_schema(self.client.introspection) + self.client._build_schema_from_introspection(execution_result) @property def transport(self):