Skip to content

Trapping dependencies Exceptions into TransportConnectionFailed #558

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/advanced/error_handling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ Here are the possible Transport Errors:
If you don't need the schema, you can try to create the client with
:code:`fetch_schema_from_transport=False`

- :class:`TransportConnectionFailed <gql.transport.exceptions.TransportConnectionFailed>`:
This exception is generated when an unexpected Exception is received from the
transport dependency when trying to connect or to send the request.
For example in case of an SSL error, or if a websocket connection suddenly fails.

- :class:`TransportClosed <gql.transport.exceptions.TransportClosed>`:
This exception is generated when the client is trying to use the transport
while the transport was previously closed.
Expand Down
9 changes: 4 additions & 5 deletions gql/gql.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@

def gql(request_string: str) -> GraphQLRequest:
"""Given a string containing a GraphQL request,
parse it into a Document and put it into a GraphQLRequest object
parse it into a Document and put it into a GraphQLRequest object.

:param request_string: the GraphQL request as a String
:return: a :class:`GraphQLRequest <gql.GraphQLRequest>`
which can be later executed or subscribed by a
:class:`Client <gql.client.Client>`, by an
:class:`async session <gql.client.AsyncClientSession>` or by a
:class:`sync session <gql.client.SyncClientSession>`

:class:`Client <gql.client.Client>`, by an
:class:`async session <gql.client.AsyncClientSession>` or by a
:class:`sync session <gql.client.SyncClientSession>`
:raises graphql.error.GraphQLError: if a syntax error is encountered.
"""
return GraphQLRequest(request_string)
11 changes: 4 additions & 7 deletions gql/graphql_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,19 @@ def __init__(
variable_values: Optional[Dict[str, Any]] = None,
operation_name: Optional[str] = None,
):
"""
Initialize a GraphQL request.
"""Initialize a GraphQL request.

:param request: GraphQL request as DocumentNode object or as a string.
If string, it will be converted to DocumentNode.
:param variable_values: Dictionary of input parameters (Default: None).
:param operation_name: Name of the operation that shall be executed.
Only required in multi-operation documents (Default: None).

:return: a :class:`GraphQLRequest <gql.GraphQLRequest>`
which can be later executed or subscribed by a
:class:`Client <gql.client.Client>`, by an
:class:`async session <gql.client.AsyncClientSession>` or by a
:class:`sync session <gql.client.SyncClientSession>`
:class:`Client <gql.client.Client>`, by an
:class:`async session <gql.client.AsyncClientSession>` or by a
:class:`sync session <gql.client.SyncClientSession>`
:raises graphql.error.GraphQLError: if a syntax error is encountered.

"""
if isinstance(request, str):
source = Source(request, "GraphQL request")
Expand Down
15 changes: 13 additions & 2 deletions gql/transport/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
from .exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportError,
TransportProtocolError,
TransportServerError,
)
Expand Down Expand Up @@ -377,6 +379,10 @@ async def execute(
try:
async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
return await self._prepare_result(resp)
except TransportError:
raise
except Exception as e:
raise TransportConnectionFailed(str(e)) from e
finally:
if upload_files:
close_files(list(self.files.values()))
Expand Down Expand Up @@ -407,8 +413,13 @@ async def execute_batch(
extra_args,
)

async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
return await self._prepare_batch_result(reqs, resp)
try:
async with self.session.post(self.url, ssl=self.ssl, **post_args) as resp:
return await self._prepare_batch_result(reqs, resp)
except TransportError:
raise
except Exception as e:
raise TransportConnectionFailed(str(e)) from e

def subscribe(
self,
Expand Down
5 changes: 3 additions & 2 deletions gql/transport/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ class TransportClosed(TransportError):


class TransportConnectionFailed(TransportError):
"""Transport adapter connection closed.
"""Transport connection failed.

This exception is by the connection adapter code when a connection closed.
This exception is by the connection adapter code when a connection closed
or if an unexpected Exception was received when trying to send a request.
"""


Expand Down
15 changes: 13 additions & 2 deletions gql/transport/httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from .exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportServerError,
)
Expand Down Expand Up @@ -262,6 +263,8 @@ def execute(

try:
response = self.client.post(self.url, **post_args)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e
finally:
if upload_files:
close_files(list(self.files.values()))
Expand Down Expand Up @@ -294,7 +297,10 @@ def execute_batch(
extra_args=extra_args,
)

response = self.client.post(self.url, **post_args)
try:
response = self.client.post(self.url, **post_args)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e

return self._prepare_batch_result(reqs, response)

Expand Down Expand Up @@ -354,6 +360,8 @@ async def execute(

try:
response = await self.client.post(self.url, **post_args)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e
finally:
if upload_files:
close_files(list(self.files.values()))
Expand Down Expand Up @@ -386,7 +394,10 @@ async def execute_batch(
extra_args=extra_args,
)

response = await self.client.post(self.url, **post_args)
try:
response = await self.client.post(self.url, **post_args)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e

return self._prepare_batch_result(reqs, response)

Expand Down
16 changes: 11 additions & 5 deletions gql/transport/requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from .exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportServerError,
)
Expand Down Expand Up @@ -289,6 +290,8 @@ def execute(
# Using the created session to perform requests
try:
response = self.session.request(self.method, self.url, **post_args)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e
finally:
if upload_files:
close_files(list(self.files.values()))
Expand Down Expand Up @@ -349,11 +352,14 @@ def execute_batch(
extra_args=extra_args,
)

response = self.session.request(
self.method,
self.url,
**post_args,
)
try:
response = self.session.request(
self.method,
self.url,
**post_args,
)
except Exception as e:
raise TransportConnectionFailed(str(e)) from e

return self._prepare_batch_result(reqs, response)

Expand Down
18 changes: 12 additions & 6 deletions tests/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from gql.transport.exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportQueryError,
TransportServerError,
Expand Down Expand Up @@ -1455,7 +1456,6 @@ async def handler(request):
async def test_aiohttp_query_https_self_cert_fail(ssl_aiohttp_server):
"""By default, we should verify the ssl certificate"""
from aiohttp import web
from aiohttp.client_exceptions import ClientConnectorCertificateError

from gql.transport.aiohttp import AIOHTTPTransport

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

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

with pytest.raises(ClientConnectorCertificateError) as exc_info:
async with Client(transport=transport) as session:
query = gql(query1_str)
query = gql(query1_str)

# Execute query asynchronously
expected_error = "certificate verify failed: self-signed certificate"

with pytest.raises(TransportConnectionFailed) as exc_info:
async with Client(transport=transport) as session:
await session.execute(query)

expected_error = "certificate verify failed: self-signed certificate"
assert expected_error in str(exc_info.value)

with pytest.raises(TransportConnectionFailed) as exc_info:
async with Client(transport=transport) as session:
await session.execute_batch([query])

assert expected_error in str(exc_info.value)

assert transport.session is None


Expand Down
16 changes: 10 additions & 6 deletions tests/test_httpx.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from gql.transport.exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportQueryError,
TransportServerError,
Expand Down Expand Up @@ -150,7 +151,6 @@ async def test_httpx_query_https_self_cert_fail(
):
"""By default, we should verify the ssl certificate"""
from aiohttp import web
from httpx import ConnectError

from gql.transport.httpx import HTTPXTransport

Expand Down Expand Up @@ -180,15 +180,19 @@ def test_code():
**extra_args,
)

with pytest.raises(ConnectError) as exc_info:
with Client(transport=transport) as session:
query = gql(query1_str)

query = gql(query1_str)
expected_error = "certificate verify failed: self-signed certificate"

# Execute query synchronously
with pytest.raises(TransportConnectionFailed) as exc_info:
with Client(transport=transport) as session:
session.execute(query)

expected_error = "certificate verify failed: self-signed certificate"
assert expected_error in str(exc_info.value)

with pytest.raises(TransportConnectionFailed) as exc_info:
with Client(transport=transport) as session:
session.execute_batch([query])

assert expected_error in str(exc_info.value)

Expand Down
16 changes: 10 additions & 6 deletions tests/test_httpx_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from gql.transport.exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportQueryError,
TransportServerError,
Expand Down Expand Up @@ -1155,7 +1156,6 @@ async def handler(request):
@pytest.mark.parametrize("verify_https", ["explicitely_enabled", "default"])
async def test_httpx_query_https_self_cert_fail(ssl_aiohttp_server, verify_https):
from aiohttp import web
from httpx import ConnectError

from gql.transport.httpx import HTTPXAsyncTransport

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

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

with pytest.raises(ConnectError) as exc_info:
async with Client(transport=transport) as session:
query = gql(query1_str)

query = gql(query1_str)
expected_error = "certificate verify failed: self-signed certificate"

# Execute query asynchronously
with pytest.raises(TransportConnectionFailed) as exc_info:
async with Client(transport=transport) as session:
await session.execute(query)

expected_error = "certificate verify failed: self-signed certificate"
assert expected_error in str(exc_info.value)

with pytest.raises(TransportConnectionFailed) as exc_info:
async with Client(transport=transport) as session:
await session.execute_batch([query])

assert expected_error in str(exc_info.value)

Expand Down
4 changes: 2 additions & 2 deletions tests/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from gql.transport.exceptions import (
TransportAlreadyConnected,
TransportClosed,
TransportConnectionFailed,
TransportProtocolError,
TransportQueryError,
TransportServerError,
Expand Down Expand Up @@ -154,7 +155,6 @@ async def test_requests_query_https_self_cert_fail(
):
"""By default, we should verify the ssl certificate"""
from aiohttp import web
from requests.exceptions import SSLError

from gql.transport.requests import RequestsHTTPTransport

Expand Down Expand Up @@ -182,7 +182,7 @@ def test_code():
**extra_args,
)

with pytest.raises(SSLError) as exc_info:
with pytest.raises(TransportConnectionFailed) as exc_info:
with Client(transport=transport) as session:

query = gql(query1_str)
Expand Down
Loading