Skip to content

Commit 9f2139b

Browse files
authored
Check for errors during fetch_schema() (#328)
1 parent 321c606 commit 9f2139b

File tree

4 files changed

+121
-7
lines changed

4 files changed

+121
-7
lines changed

docs/advanced/error_handling.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ Here are the possible Transport Errors:
4141
The message of the exception contains the first error returned by the backend.
4242
All the errors messages are available in the exception :code:`errors` attribute.
4343

44+
If the error message begins with :code:`Error while fetching schema:`, it means
45+
that gql was not able to get the schema from the backend.
46+
If you don't need the schema, you can try to create the client with
47+
:code:`fetch_schema_from_transport=False`
48+
4449
- :class:`TransportClosed <gql.transport.exceptions.TransportClosed>`:
4550
This exception is generated when the client is trying to use the transport
4651
while the transport was previously closed.

gql/client.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import asyncio
22
import sys
33
import warnings
4-
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union, overload
4+
from typing import Any, AsyncGenerator, Dict, Generator, Optional, Union, cast, overload
55

66
from graphql import (
77
DocumentNode,
88
ExecutionResult,
99
GraphQLSchema,
10+
IntrospectionQuery,
1011
build_ast_schema,
1112
get_introspection_query,
1213
parse,
@@ -55,7 +56,7 @@ class Client:
5556
def __init__(
5657
self,
5758
schema: Optional[Union[str, GraphQLSchema]] = None,
58-
introspection=None,
59+
introspection: Optional[IntrospectionQuery] = None,
5960
transport: Optional[Union[Transport, AsyncTransport]] = None,
6061
fetch_schema_from_transport: bool = False,
6162
execute_timeout: Optional[Union[int, float]] = 10,
@@ -106,7 +107,7 @@ def __init__(
106107
self.schema: Optional[GraphQLSchema] = schema
107108

108109
# Answer of the introspection query
109-
self.introspection = introspection
110+
self.introspection: Optional[IntrospectionQuery] = introspection
110111

111112
# GraphQL transport chosen
112113
self.transport: Optional[Union[Transport, AsyncTransport]] = transport
@@ -131,6 +132,22 @@ def validate(self, document: DocumentNode):
131132
if validation_errors:
132133
raise validation_errors[0]
133134

135+
def _build_schema_from_introspection(self, execution_result: ExecutionResult):
136+
if execution_result.errors:
137+
raise TransportQueryError(
138+
(
139+
f"Error while fetching schema: {execution_result.errors[0]!s}\n"
140+
"If you don't need the schema, you can try with: "
141+
'"fetch_schema_from_transport=False"'
142+
),
143+
errors=execution_result.errors,
144+
data=execution_result.data,
145+
extensions=execution_result.extensions,
146+
)
147+
148+
self.introspection = cast(IntrospectionQuery, execution_result.data)
149+
self.schema = build_client_schema(self.introspection)
150+
134151
@overload
135152
def execute_sync(
136153
self,
@@ -802,8 +819,8 @@ def fetch_schema(self) -> None:
802819
Don't use this function and instead set the fetch_schema_from_transport
803820
attribute to True"""
804821
execution_result = self.transport.execute(parse(get_introspection_query()))
805-
self.client.introspection = execution_result.data
806-
self.client.schema = build_client_schema(self.client.introspection)
822+
823+
self.client._build_schema_from_introspection(execution_result)
807824

808825
@property
809826
def transport(self):
@@ -1175,8 +1192,8 @@ async def fetch_schema(self) -> None:
11751192
execution_result = await self.transport.execute(
11761193
parse(get_introspection_query())
11771194
)
1178-
self.client.introspection = execution_result.data
1179-
self.client.schema = build_client_schema(self.client.introspection)
1195+
1196+
self.client._build_schema_from_introspection(execution_result)
11801197

11811198
@property
11821199
def transport(self):

tests/test_aiohttp.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1190,3 +1190,46 @@ async def handler(request):
11901190
africa = continents[0]
11911191

11921192
assert africa["code"] == "AF"
1193+
1194+
1195+
@pytest.mark.asyncio
1196+
async def test_aiohttp_error_fetching_schema(event_loop, aiohttp_server):
1197+
from aiohttp import web
1198+
from gql.transport.aiohttp import AIOHTTPTransport
1199+
1200+
error_answer = """
1201+
{
1202+
"errors": [
1203+
{
1204+
"errorType": "UnauthorizedException",
1205+
"message": "Permission denied"
1206+
}
1207+
]
1208+
}
1209+
"""
1210+
1211+
async def handler(request):
1212+
return web.Response(
1213+
text=error_answer,
1214+
content_type="application/json",
1215+
)
1216+
1217+
app = web.Application()
1218+
app.router.add_route("POST", "/", handler)
1219+
server = await aiohttp_server(app)
1220+
1221+
url = server.make_url("/")
1222+
1223+
transport = AIOHTTPTransport(url=url, timeout=10)
1224+
1225+
with pytest.raises(TransportQueryError) as exc_info:
1226+
async with Client(transport=transport, fetch_schema_from_transport=True):
1227+
pass
1228+
1229+
expected_error = (
1230+
"Error while fetching schema: "
1231+
"{'errorType': 'UnauthorizedException', 'message': 'Permission denied'}"
1232+
)
1233+
1234+
assert expected_error in str(exc_info.value)
1235+
assert transport.session is None

tests/test_requests.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,3 +755,52 @@ def test_code():
755755
f2.close()
756756

757757
await run_sync_test(event_loop, server, test_code)
758+
759+
760+
@pytest.mark.aiohttp
761+
@pytest.mark.asyncio
762+
async def test_requests_error_fetching_schema(
763+
event_loop, aiohttp_server, run_sync_test
764+
):
765+
from aiohttp import web
766+
from gql.transport.requests import RequestsHTTPTransport
767+
768+
error_answer = """
769+
{
770+
"errors": [
771+
{
772+
"errorType": "UnauthorizedException",
773+
"message": "Permission denied"
774+
}
775+
]
776+
}
777+
"""
778+
779+
async def handler(request):
780+
return web.Response(
781+
text=error_answer,
782+
content_type="application/json",
783+
)
784+
785+
app = web.Application()
786+
app.router.add_route("POST", "/", handler)
787+
server = await aiohttp_server(app)
788+
789+
url = server.make_url("/")
790+
791+
def test_code():
792+
transport = RequestsHTTPTransport(url=url)
793+
794+
with pytest.raises(TransportQueryError) as exc_info:
795+
with Client(transport=transport, fetch_schema_from_transport=True):
796+
pass
797+
798+
expected_error = (
799+
"Error while fetching schema: "
800+
"{'errorType': 'UnauthorizedException', 'message': 'Permission denied'}"
801+
)
802+
803+
assert expected_error in str(exc_info.value)
804+
assert transport.session is None
805+
806+
await run_sync_test(event_loop, server, test_code)

0 commit comments

Comments
 (0)