Skip to content

Commit 4af703e

Browse files
authored
Trapping dependencies Exceptions into TransportConnectionFailed (#558)
1 parent 4fa1553 commit 4af703e

File tree

11 files changed

+87
-43
lines changed

11 files changed

+87
-43
lines changed

docs/advanced/error_handling.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ Here are the possible Transport Errors:
4646
If you don't need the schema, you can try to create the client with
4747
:code:`fetch_schema_from_transport=False`
4848

49+
- :class:`TransportConnectionFailed <gql.transport.exceptions.TransportConnectionFailed>`:
50+
This exception is generated when an unexpected Exception is received from the
51+
transport dependency when trying to connect or to send the request.
52+
For example in case of an SSL error, or if a websocket connection suddenly fails.
53+
4954
- :class:`TransportClosed <gql.transport.exceptions.TransportClosed>`:
5055
This exception is generated when the client is trying to use the transport
5156
while the transport was previously closed.

gql/gql.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33

44
def gql(request_string: str) -> GraphQLRequest:
55
"""Given a string containing a GraphQL request,
6-
parse it into a Document and put it into a GraphQLRequest object
6+
parse it into a Document and put it into a GraphQLRequest object.
77
88
:param request_string: the GraphQL request as a String
99
:return: a :class:`GraphQLRequest <gql.GraphQLRequest>`
1010
which can be later executed or subscribed by a
11-
:class:`Client <gql.client.Client>`, by an
12-
:class:`async session <gql.client.AsyncClientSession>` or by a
13-
:class:`sync session <gql.client.SyncClientSession>`
14-
11+
:class:`Client <gql.client.Client>`, by an
12+
:class:`async session <gql.client.AsyncClientSession>` or by a
13+
:class:`sync session <gql.client.SyncClientSession>`
1514
:raises graphql.error.GraphQLError: if a syntax error is encountered.
1615
"""
1716
return GraphQLRequest(request_string)

gql/graphql_request.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,19 @@ def __init__(
1414
variable_values: Optional[Dict[str, Any]] = None,
1515
operation_name: Optional[str] = None,
1616
):
17-
"""
18-
Initialize a GraphQL request.
17+
"""Initialize a GraphQL request.
1918
2019
:param request: GraphQL request as DocumentNode object or as a string.
2120
If string, it will be converted to DocumentNode.
2221
:param variable_values: Dictionary of input parameters (Default: None).
2322
:param operation_name: Name of the operation that shall be executed.
2423
Only required in multi-operation documents (Default: None).
25-
2624
:return: a :class:`GraphQLRequest <gql.GraphQLRequest>`
2725
which can be later executed or subscribed by a
28-
:class:`Client <gql.client.Client>`, by an
29-
:class:`async session <gql.client.AsyncClientSession>` or by a
30-
:class:`sync session <gql.client.SyncClientSession>`
26+
:class:`Client <gql.client.Client>`, by an
27+
:class:`async session <gql.client.AsyncClientSession>` or by a
28+
:class:`sync session <gql.client.SyncClientSession>`
3129
:raises graphql.error.GraphQLError: if a syntax error is encountered.
32-
3330
"""
3431
if isinstance(request, str):
3532
source = Source(request, "GraphQL request")

gql/transport/aiohttp.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from .exceptions import (
3232
TransportAlreadyConnected,
3333
TransportClosed,
34+
TransportConnectionFailed,
35+
TransportError,
3436
TransportProtocolError,
3537
TransportServerError,
3638
)
@@ -377,6 +379,10 @@ async def execute(
377379
try:
378380
async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
379381
return await self._prepare_result(resp)
382+
except TransportError:
383+
raise
384+
except Exception as e:
385+
raise TransportConnectionFailed(str(e)) from e
380386
finally:
381387
if upload_files:
382388
close_files(list(self.files.values()))
@@ -407,8 +413,13 @@ async def execute_batch(
407413
extra_args,
408414
)
409415

410-
async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
411-
return await self._prepare_batch_result(reqs, resp)
416+
try:
417+
async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
418+
return await self._prepare_batch_result(reqs, resp)
419+
except TransportError:
420+
raise
421+
except Exception as e:
422+
raise TransportConnectionFailed(str(e)) from e
412423

413424
def subscribe(
414425
self,

gql/transport/exceptions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ class TransportClosed(TransportError):
6262

6363

6464
class TransportConnectionFailed(TransportError):
65-
"""Transport adapter connection closed.
65+
"""Transport connection failed.
6666
67-
This exception is by the connection adapter code when a connection closed.
67+
This exception is by the connection adapter code when a connection closed
68+
or if an unexpected Exception was received when trying to send a request.
6869
"""
6970

7071

gql/transport/httpx.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from .exceptions import (
2424
TransportAlreadyConnected,
2525
TransportClosed,
26+
TransportConnectionFailed,
2627
TransportProtocolError,
2728
TransportServerError,
2829
)
@@ -262,6 +263,8 @@ def execute(
262263

263264
try:
264265
response = self.client.post(self.url, **post_args)
266+
except Exception as e:
267+
raise TransportConnectionFailed(str(e)) from e
265268
finally:
266269
if upload_files:
267270
close_files(list(self.files.values()))
@@ -294,7 +297,10 @@ def execute_batch(
294297
extra_args=extra_args,
295298
)
296299

297-
response = self.client.post(self.url, **post_args)
300+
try:
301+
response = self.client.post(self.url, **post_args)
302+
except Exception as e:
303+
raise TransportConnectionFailed(str(e)) from e
298304

299305
return self._prepare_batch_result(reqs, response)
300306

@@ -354,6 +360,8 @@ async def execute(
354360

355361
try:
356362
response = await self.client.post(self.url, **post_args)
363+
except Exception as e:
364+
raise TransportConnectionFailed(str(e)) from e
357365
finally:
358366
if upload_files:
359367
close_files(list(self.files.values()))
@@ -386,7 +394,10 @@ async def execute_batch(
386394
extra_args=extra_args,
387395
)
388396

389-
response = await self.client.post(self.url, **post_args)
397+
try:
398+
response = await self.client.post(self.url, **post_args)
399+
except Exception as e:
400+
raise TransportConnectionFailed(str(e)) from e
390401

391402
return self._prepare_batch_result(reqs, response)
392403

gql/transport/requests.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from .exceptions import (
3030
TransportAlreadyConnected,
3131
TransportClosed,
32+
TransportConnectionFailed,
3233
TransportProtocolError,
3334
TransportServerError,
3435
)
@@ -289,6 +290,8 @@ def execute(
289290
# Using the created session to perform requests
290291
try:
291292
response = self.session.request(self.method, self.url, **post_args)
293+
except Exception as e:
294+
raise TransportConnectionFailed(str(e)) from e
292295
finally:
293296
if upload_files:
294297
close_files(list(self.files.values()))
@@ -349,11 +352,14 @@ def execute_batch(
349352
extra_args=extra_args,
350353
)
351354

352-
response = self.session.request(
353-
self.method,
354-
self.url,
355-
**post_args,
356-
)
355+
try:
356+
response = self.session.request(
357+
self.method,
358+
self.url,
359+
**post_args,
360+
)
361+
except Exception as e:
362+
raise TransportConnectionFailed(str(e)) from e
357363

358364
return self._prepare_batch_result(reqs, response)
359365

tests/test_aiohttp.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from gql.transport.exceptions import (
1212
TransportAlreadyConnected,
1313
TransportClosed,
14+
TransportConnectionFailed,
1415
TransportProtocolError,
1516
TransportQueryError,
1617
TransportServerError,
@@ -1455,7 +1456,6 @@ async def handler(request):
14551456
async def test_aiohttp_query_https_self_cert_fail(ssl_aiohttp_server):
14561457
"""By default, we should verify the ssl certificate"""
14571458
from aiohttp import web
1458-
from aiohttp.client_exceptions import ClientConnectorCertificateError
14591459

14601460
from gql.transport.aiohttp import AIOHTTPTransport
14611461

@@ -1472,16 +1472,22 @@ async def handler(request):
14721472

14731473
transport = AIOHTTPTransport(url=url, timeout=10)
14741474

1475-
with pytest.raises(ClientConnectorCertificateError) as exc_info:
1476-
async with Client(transport=transport) as session:
1477-
query = gql(query1_str)
1475+
query = gql(query1_str)
14781476

1479-
# Execute query asynchronously
1477+
expected_error = "certificate verify failed: self-signed certificate"
1478+
1479+
with pytest.raises(TransportConnectionFailed) as exc_info:
1480+
async with Client(transport=transport) as session:
14801481
await session.execute(query)
14811482

1482-
expected_error = "certificate verify failed: self-signed certificate"
1483+
assert expected_error in str(exc_info.value)
1484+
1485+
with pytest.raises(TransportConnectionFailed) as exc_info:
1486+
async with Client(transport=transport) as session:
1487+
await session.execute_batch([query])
14831488

14841489
assert expected_error in str(exc_info.value)
1490+
14851491
assert transport.session is None
14861492

14871493

tests/test_httpx.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from gql.transport.exceptions import (
88
TransportAlreadyConnected,
99
TransportClosed,
10+
TransportConnectionFailed,
1011
TransportProtocolError,
1112
TransportQueryError,
1213
TransportServerError,
@@ -150,7 +151,6 @@ async def test_httpx_query_https_self_cert_fail(
150151
):
151152
"""By default, we should verify the ssl certificate"""
152153
from aiohttp import web
153-
from httpx import ConnectError
154154

155155
from gql.transport.httpx import HTTPXTransport
156156

@@ -180,15 +180,19 @@ def test_code():
180180
**extra_args,
181181
)
182182

183-
with pytest.raises(ConnectError) as exc_info:
184-
with Client(transport=transport) as session:
183+
query = gql(query1_str)
185184

186-
query = gql(query1_str)
185+
expected_error = "certificate verify failed: self-signed certificate"
187186

188-
# Execute query synchronously
187+
with pytest.raises(TransportConnectionFailed) as exc_info:
188+
with Client(transport=transport) as session:
189189
session.execute(query)
190190

191-
expected_error = "certificate verify failed: self-signed certificate"
191+
assert expected_error in str(exc_info.value)
192+
193+
with pytest.raises(TransportConnectionFailed) as exc_info:
194+
with Client(transport=transport) as session:
195+
session.execute_batch([query])
192196

193197
assert expected_error in str(exc_info.value)
194198

tests/test_httpx_async.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from gql.transport.exceptions import (
1010
TransportAlreadyConnected,
1111
TransportClosed,
12+
TransportConnectionFailed,
1213
TransportProtocolError,
1314
TransportQueryError,
1415
TransportServerError,
@@ -1155,7 +1156,6 @@ async def handler(request):
11551156
@pytest.mark.parametrize("verify_https", ["explicitely_enabled", "default"])
11561157
async def test_httpx_query_https_self_cert_fail(ssl_aiohttp_server, verify_https):
11571158
from aiohttp import web
1158-
from httpx import ConnectError
11591159

11601160
from gql.transport.httpx import HTTPXAsyncTransport
11611161

@@ -1177,15 +1177,19 @@ async def handler(request):
11771177

11781178
transport = HTTPXAsyncTransport(url=url, timeout=10, **extra_args)
11791179

1180-
with pytest.raises(ConnectError) as exc_info:
1181-
async with Client(transport=transport) as session:
1180+
query = gql(query1_str)
11821181

1183-
query = gql(query1_str)
1182+
expected_error = "certificate verify failed: self-signed certificate"
11841183

1185-
# Execute query asynchronously
1184+
with pytest.raises(TransportConnectionFailed) as exc_info:
1185+
async with Client(transport=transport) as session:
11861186
await session.execute(query)
11871187

1188-
expected_error = "certificate verify failed: self-signed certificate"
1188+
assert expected_error in str(exc_info.value)
1189+
1190+
with pytest.raises(TransportConnectionFailed) as exc_info:
1191+
async with Client(transport=transport) as session:
1192+
await session.execute_batch([query])
11891193

11901194
assert expected_error in str(exc_info.value)
11911195

tests/test_requests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from gql.transport.exceptions import (
99
TransportAlreadyConnected,
1010
TransportClosed,
11+
TransportConnectionFailed,
1112
TransportProtocolError,
1213
TransportQueryError,
1314
TransportServerError,
@@ -154,7 +155,6 @@ async def test_requests_query_https_self_cert_fail(
154155
):
155156
"""By default, we should verify the ssl certificate"""
156157
from aiohttp import web
157-
from requests.exceptions import SSLError
158158

159159
from gql.transport.requests import RequestsHTTPTransport
160160

@@ -182,7 +182,7 @@ def test_code():
182182
**extra_args,
183183
)
184184

185-
with pytest.raises(SSLError) as exc_info:
185+
with pytest.raises(TransportConnectionFailed) as exc_info:
186186
with Client(transport=transport) as session:
187187

188188
query = gql(query1_str)

0 commit comments

Comments
 (0)