Skip to content

Commit c5f9607

Browse files
authored
chore: better error messages on http client errors (#1017)
1 parent 4f0ae58 commit c5f9607

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

components/renku_data_services/base_api/error_handler.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from sqlite3 import Error as SqliteError
99
from typing import Any, Optional, Protocol, TypeVar, Union
1010

11+
import httpx
1112
import jwt
1213
from asyncpg import exceptions as postgres_exceptions
1314
from pydantic import ValidationError as PydanticValidationError
@@ -67,9 +68,9 @@ def __init__(self, api_spec: ApiSpec, base: type[BaseRenderer] = TextRenderer) -
6768
self.api_spec = api_spec
6869
super().__init__(base)
6970

70-
def default(self, request: Request, exception: Exception) -> HTTPResponse:
71-
"""Overrides the default error handler."""
72-
formatted_exception = errors.BaseError()
71+
@classmethod
72+
def _get_formatted_exception(cls, request: Request, exception: Exception) -> errors.BaseError | None:
73+
formatted_exception: errors.BaseError | None = None
7374
match exception:
7475
case errors.BaseError():
7576
formatted_exception = exception
@@ -136,6 +137,24 @@ def default(self, request: Request, exception: Exception) -> HTTPResponse:
136137
case CancelledError():
137138
quiet = request.transport.is_closing()
138139
formatted_exception = errors.RequestCancelledError(quiet=quiet)
140+
141+
case httpx.RequestError():
142+
req_uri = "<unknown-uri>"
143+
req_method = "<unknown-method>"
144+
if exception._request:
145+
req_uri = str(exception.request.url)
146+
req_method = exception.request.method
147+
148+
formatted_exception = errors.BaseError(
149+
message=f"Error on remote connection {req_method} {req_uri}: {exception}"
150+
)
151+
152+
return formatted_exception
153+
154+
def default(self, request: Request, exception: Exception) -> HTTPResponse:
155+
"""Overrides the default error handler."""
156+
formatted_exception = self._get_formatted_exception(request, exception) or errors.BaseError()
157+
139158
self.log(request, formatted_exception)
140159
if formatted_exception.status_code == 500 and "PYTEST_CURRENT_TEST" in os.environ:
141160
# TODO: Figure out how to do logging properly in here, I could not get the sanic logs to show up from here
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Test functions for the error_handler module."""
2+
3+
from sqlite3 import Error as SqliteError
4+
5+
import httpx
6+
import jwt
7+
from asyncpg.exceptions import PostgresError
8+
from pydantic import ValidationError as PydanticValidationError
9+
from sanic import SanicException
10+
from sanic_ext.exceptions import ValidationError as SanicValidationError
11+
from sqlalchemy.exc import SQLAlchemyError
12+
13+
from renku_data_services.base_api.error_handler import CustomErrorHandler
14+
from renku_data_services.errors import errors
15+
16+
17+
def make_sqlite_error() -> SqliteError:
18+
err = SqliteError()
19+
err.sqlite_errorcode = 1
20+
err.sqlite_errorname = "name"
21+
return err
22+
23+
24+
def make_pg_error() -> PostgresError:
25+
err = PostgresError()
26+
err.msg = "not supported"
27+
err.pgcode = "A0110"
28+
return err
29+
30+
31+
def test_match_exception() -> None:
32+
expect_to_match = [
33+
errors.BaseError(),
34+
SanicValidationError(extra={"exception": PydanticValidationError("oops", [])}),
35+
SanicValidationError(extra={"exception": TypeError("oops")}),
36+
PydanticValidationError("oops", []),
37+
SanicException(),
38+
make_sqlite_error(),
39+
make_pg_error(),
40+
SQLAlchemyError(),
41+
OverflowError(),
42+
jwt.exceptions.InvalidTokenError(),
43+
httpx.ConnectError("oops"),
44+
httpx.UnsupportedProtocol("ftp"),
45+
]
46+
47+
req = httpx.Request("GET", "")
48+
for exc in expect_to_match:
49+
result = CustomErrorHandler._get_formatted_exception(req, exc)
50+
assert result is not None

0 commit comments

Comments
 (0)