-
Notifications
You must be signed in to change notification settings - Fork 16
Stop hiding cause of last exception #224
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,5 @@ python: | |
- develop | ||
|
||
sphinx: | ||
configuration: docs/sphinx/conf.py | ||
fail_on_warning: true |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,9 +50,6 @@ def __repr__(self) -> str: | |
parts.append(f"errors={self.errors!r}") | ||
return "{}({})".format(self.__class__.__name__, ", ".join(parts)) | ||
|
||
def __str__(self) -> str: | ||
return str(self.message) | ||
|
||
|
||
class SniffingError(TransportError): | ||
"""Error that occurs during the sniffing of nodes""" | ||
|
@@ -67,29 +64,14 @@ class SerializationError(TransportError): | |
class ConnectionError(TransportError): | ||
"""Error raised by the HTTP connection""" | ||
|
||
def __str__(self) -> str: | ||
if self.errors: | ||
return f"Connection error caused by: {self.errors[0].__class__.__name__}({self.errors[0]})" | ||
return "Connection error" | ||
|
||
Comment on lines
-70
to
-74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
|
||
class TlsError(ConnectionError): | ||
"""Error raised by during the TLS handshake""" | ||
|
||
def __str__(self) -> str: | ||
if self.errors: | ||
return f"TLS error caused by: {self.errors[0].__class__.__name__}({self.errors[0]})" | ||
return "TLS error" | ||
|
||
|
||
class ConnectionTimeout(TransportError): | ||
"""Connection timed out during an operation""" | ||
|
||
def __str__(self) -> str: | ||
if self.errors: | ||
return f"Connection timeout caused by: {self.errors[0].__class__.__name__}({self.errors[0]})" | ||
return "Connection timed out" | ||
|
||
|
||
class ApiError(Exception): | ||
"""Base-class for clients that raise errors due to a response such as '404 Not Found'""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,6 +45,14 @@ | |
from tests.conftest import AsyncDummyNode | ||
|
||
|
||
def exception_to_dict(exc: TransportError) -> dict: | ||
return { | ||
"type": exc.__class__.__name__, | ||
"message": exc.message, | ||
"errors": [exception_to_dict(e) for e in exc.errors], | ||
} | ||
Comment on lines
+48
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is all the information exposed by |
||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_async_transport_httpbin(httpbin_node_config): | ||
t = AsyncTransport([httpbin_node_config], meta_header=False) | ||
|
@@ -139,14 +147,39 @@ async def test_request_will_fail_after_x_retries(): | |
) | ||
], | ||
node_class=AsyncDummyNode, | ||
max_retries=0, | ||
) | ||
|
||
with pytest.raises(ConnectionError) as e: | ||
await t.perform_request("GET", "/") | ||
|
||
assert exception_to_dict(e.value) == { | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
} | ||
|
||
# max_retries=2 | ||
with pytest.raises(ConnectionError) as e: | ||
await t.perform_request("GET", "/", max_retries=2) | ||
|
||
assert 4 == len(t.node_pool.get().calls) | ||
assert len(e.value.errors) == 3 | ||
assert all(isinstance(error, ConnectionError) for error in e.value.errors) | ||
assert exception_to_dict(e.value) == { | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [ | ||
{ | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
}, | ||
{ | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
}, | ||
], | ||
} | ||
|
||
|
||
@pytest.mark.parametrize("retry_on_timeout", [True, False]) | ||
|
@@ -174,15 +207,30 @@ async def test_retry_on_timeout(retry_on_timeout): | |
) | ||
|
||
if retry_on_timeout: | ||
with pytest.raises(ConnectionError) as e: | ||
with pytest.raises(TransportError) as e: | ||
await t.perform_request("GET", "/") | ||
assert len(e.value.errors) == 1 | ||
assert isinstance(e.value.errors[0], ConnectionTimeout) | ||
|
||
assert exception_to_dict(e.value) == { | ||
"type": "ConnectionError", | ||
"message": "error!", | ||
"errors": [ | ||
{ | ||
"type": "ConnectionTimeout", | ||
"message": "abandon ship", | ||
"errors": [], | ||
} | ||
], | ||
} | ||
|
||
else: | ||
with pytest.raises(ConnectionTimeout) as e: | ||
with pytest.raises(TransportError) as e: | ||
await t.perform_request("GET", "/") | ||
assert len(e.value.errors) == 0 | ||
|
||
assert exception_to_dict(e.value) == { | ||
"type": "ConnectionTimeout", | ||
"message": "abandon ship", | ||
"errors": [], | ||
} | ||
|
||
|
||
@pytest.mark.asyncio | ||
|
@@ -254,8 +302,27 @@ async def test_failed_connection_will_be_marked_as_dead(): | |
await t.perform_request("GET", "/") | ||
assert 0 == len(t.node_pool._alive_nodes) | ||
assert 2 == len(t.node_pool._dead_nodes.queue) | ||
assert len(e.value.errors) == 3 | ||
assert all(isinstance(error, ConnectionError) for error in e.value.errors) | ||
assert exception_to_dict(e.value) == { | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [ | ||
{ | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
}, | ||
{ | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
}, | ||
{ | ||
"type": "ConnectionError", | ||
"message": "abandon ship", | ||
"errors": [], | ||
}, | ||
], | ||
} | ||
|
||
|
||
@pytest.mark.asyncio | ||
|
@@ -602,7 +669,12 @@ def sniff_error(*_): | |
|
||
with pytest.raises(TransportError) as e: | ||
await t.perform_request("GET", "/") | ||
assert str(e.value) == "This is an error!" | ||
|
||
assert exception_to_dict(e.value) == { | ||
"type": "TransportError", | ||
"message": "This is an error!", | ||
"errors": [], | ||
} | ||
|
||
assert t._last_sniffed_at == last_sniffed_at | ||
assert t._sniffing_task.done() | ||
|
@@ -628,9 +700,11 @@ async def test_sniff_on_start_no_results_errors(sniff_callback): | |
with pytest.raises(SniffingError) as e: | ||
await t._async_call() | ||
|
||
assert ( | ||
str(e.value) == "No viable nodes were discovered on the initial sniff attempt" | ||
) | ||
assert exception_to_dict(e.value) == { | ||
"type": "SniffingError", | ||
"message": "No viable nodes were discovered on the initial sniff attempt", | ||
"errors": [], | ||
} | ||
|
||
|
||
@pytest.mark.parametrize("pool_size", [1, 8]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
message
is passed insuper.__init__(message)
and we removed the custom__str__
implementation below, this is already the default implementation.In Python,
__str__
isn't helpful. If you need to know the class name, then__repr__
is required, and our__repr__
implementation contains all information about the current exception, includingerrors
.