Skip to content

Commit 1077971

Browse files
committed
MOD: Improve client handling of 408/504 errors
1 parent 1eb7dfb commit 1077971

File tree

8 files changed

+131
-52
lines changed

8 files changed

+131
-52
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- Added `instrument_class`, `strike_price`, and `strike_price_currency` to definition
1010
schema
1111
- Added support for `imbalance` schema
12+
- Improved exception messages for server and client timeouts
1213

1314
## 0.9.0 - 2023-03-10
1415
- Removed `record_count` property from Bento class

databento/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@
1818
SType,
1919
SymbologyResolution,
2020
)
21-
from databento.historical.api import API_VERSION
22-
from databento.historical.client import Historical
23-
from databento.historical.error import (
21+
from databento.common.error import (
2422
BentoClientError,
2523
BentoError,
2624
BentoHttpError,
2725
BentoServerError,
2826
)
27+
from databento.historical.api import API_VERSION
28+
from databento.historical.client import Historical
2929
from databento.version import __version__ # noqa
3030

3131

databento/common/dbnstore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
STRUCT_MAP,
3131
)
3232
from databento.common.enums import Compression, Schema, SType
33+
from databento.common.error import BentoError
3334
from databento.common.metadata import MetadataDecoder
3435
from databento.common.symbology import ProductIdMappingInterval
35-
from databento.historical.error import BentoError
3636

3737

3838
logger = logging.getLogger(__name__)
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ class BentoError(Exception):
66
Represents a Databento specific error.
77
"""
88

9-
pass
10-
119

1210
class BentoHttpError(BentoError):
1311
"""
@@ -105,5 +103,3 @@ class BentoWarning(Warning):
105103
"""
106104
Represents a Databento specific warning.
107105
"""
108-
109-
pass

databento/historical/api/timeseries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
from databento.common.dbnstore import DBNStore
1111
from databento.common.deprecated import deprecated
1212
from databento.common.enums import Compression, Dataset, Encoding, Schema, SType
13+
from databento.common.error import BentoWarning
1314
from databento.common.parsing import datetime_to_string, optional_symbols_list_to_string
1415
from databento.common.validation import validate_enum, validate_semantic_string
1516
from databento.historical.api import API_VERSION
16-
from databento.historical.error import BentoWarning
1717
from databento.historical.http import BentoHttpAPI
1818

1919

databento/historical/http.py

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
import aiohttp
77
import requests
8-
from aiohttp import ClientResponse
9-
from databento.historical.error import BentoClientError, BentoServerError
8+
from aiohttp import ClientResponse, ContentTypeError
9+
from databento.common.error import BentoClientError, BentoServerError
1010
from databento.version import __version__
1111
from requests import Response
1212
from requests.auth import HTTPBasicAuth
@@ -155,6 +155,8 @@ def check_http_error(response: Response) -> None:
155155
except JSONDecodeError:
156156
json_body = None
157157
message = None
158+
if response.status_code == 504:
159+
message = "The remote gateway timed out."
158160
raise BentoServerError(
159161
http_status=response.status_code,
160162
http_body=response.content,
@@ -169,6 +171,8 @@ def check_http_error(response: Response) -> None:
169171
except JSONDecodeError:
170172
json_body = None
171173
message = None
174+
if response.status_code == 408:
175+
message = "The request transmission timed out."
172176
raise BentoClientError(
173177
http_status=response.status_code,
174178
http_body=response.content,
@@ -180,22 +184,40 @@ def check_http_error(response: Response) -> None:
180184

181185
async def check_http_error_async(response: ClientResponse) -> None:
182186
if is_500_series_error(response.status):
183-
json_body = await response.json()
184-
http_body = await response.read()
187+
try:
188+
json_body = await response.json()
189+
http_body = await response.read()
190+
message = json_body.get("detail", "")
191+
except ContentTypeError:
192+
http_body = None
193+
json_body = None
194+
message = ""
195+
196+
if response.status == 504:
197+
message = "The remote gateway timed out."
185198
raise BentoServerError(
186199
http_status=response.status,
187200
http_body=http_body,
188201
json_body=json_body,
189-
message=json_body["detail"],
202+
message=message,
190203
headers=response.headers,
191204
)
192-
elif is_400_series_error(response.status):
193-
json_body = await response.json()
194-
http_body = await response.read()
205+
206+
if is_400_series_error(response.status):
207+
try:
208+
json_body = await response.json()
209+
http_body = await response.read()
210+
message = json_body.get("detail", "")
211+
except ContentTypeError:
212+
http_body = None
213+
json_body = None
214+
message = ""
215+
if response.status == 408:
216+
message = "The request transmission timed out."
195217
raise BentoClientError(
196218
http_status=response.status,
197219
http_body=http_body,
198220
json_body=json_body,
199-
message=json_body["detail"],
221+
message=message,
200222
headers=response.headers,
201223
)

tests/test_historical_bento.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from databento.common.data import DEFINITION_DROP_COLUMNS
1313
from databento.common.dbnstore import DBNStore
1414
from databento.common.enums import Schema, SType
15-
from databento.historical.error import BentoError
15+
from databento.common.error import BentoError
1616

1717

1818
def test_from_file_when_not_exists_raises_expected_exception() -> None:

tests/test_historical_error.py

Lines changed: 93 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,93 @@
1-
from databento.historical.error import BentoClientError, BentoServerError
2-
3-
4-
class TestHistoricalHttpError:
5-
def test_client_error_str_and_repr(self) -> None:
6-
# Arrange, Act
7-
error = BentoClientError(
8-
http_status=400,
9-
http_body=None,
10-
message="Bad Request",
11-
)
12-
13-
# Assert
14-
assert str(error) == "400 Bad Request"
15-
assert (
16-
repr(error)
17-
== "BentoClientError(request_id=None, http_status=400, message=Bad Request)" # noqa
18-
)
19-
20-
def test_server_error_str_and_repr(self) -> None:
21-
# Arrange, Act
22-
error = BentoServerError(
23-
http_status=500,
24-
http_body=None,
25-
message="Internal Server Error",
26-
)
27-
28-
# Assert
29-
assert str(error) == "500 Internal Server Error"
30-
assert (
31-
repr(error)
32-
== "BentoServerError(request_id=None, http_status=500, message=Internal Server Error)" # noqa
33-
)
1+
import sys
2+
from typing import Type
3+
from unittest.mock import MagicMock
4+
5+
import aiohttp
6+
import pytest
7+
import requests
8+
from databento.common.error import BentoClientError, BentoServerError
9+
from databento.historical.http import check_http_error, check_http_error_async
10+
11+
12+
@pytest.mark.parametrize(
13+
"status_code, expected_exception, message",
14+
[
15+
pytest.param(404, BentoClientError, r"", id="404"),
16+
pytest.param(408, BentoClientError, r"timed out.$", id="408"),
17+
pytest.param(500, BentoServerError, r"", id="500"),
18+
pytest.param(504, BentoServerError, r"timed out.$", id="504"),
19+
],
20+
)
21+
def test_check_http_status(
22+
status_code: int,
23+
expected_exception: Type[Exception],
24+
message: str,
25+
) -> None:
26+
"""
27+
Test that responses with the given status code raise the expected exception.
28+
"""
29+
response = requests.Response()
30+
response.status_code = status_code
31+
with pytest.raises(expected_exception) as exc:
32+
check_http_error(response)
33+
34+
exc.match(message)
35+
36+
37+
@pytest.mark.asyncio
38+
@pytest.mark.skipif(sys.version_info < (3, 8), reason="no async support for MagicMock")
39+
@pytest.mark.parametrize(
40+
"status_code, expected_exception, message",
41+
[
42+
pytest.param(404, BentoClientError, r"", id="404"),
43+
pytest.param(408, BentoClientError, r"timed out.$", id="408"),
44+
pytest.param(500, BentoServerError, r"", id="500"),
45+
pytest.param(504, BentoServerError, r"timed out.$", id="504"),
46+
],
47+
)
48+
async def test_check_http_status_async(
49+
status_code: int,
50+
expected_exception: Type[Exception],
51+
message: str,
52+
) -> None:
53+
"""
54+
Test that responses with the given status code raise the expected exception.
55+
"""
56+
response = MagicMock(spec=aiohttp.ClientResponse)
57+
response.status = status_code
58+
with pytest.raises(expected_exception) as exc:
59+
await check_http_error_async(response)
60+
61+
exc.match(message)
62+
63+
64+
def test_client_error_str_and_repr() -> None:
65+
# Arrange, Act
66+
error = BentoClientError(
67+
http_status=400,
68+
http_body=None,
69+
message="Bad Request",
70+
)
71+
72+
# Assert
73+
assert str(error) == "400 Bad Request"
74+
assert (
75+
repr(error)
76+
== "BentoClientError(request_id=None, http_status=400, message=Bad Request)" # noqa
77+
)
78+
79+
80+
def test_server_error_str_and_repr() -> None:
81+
# Arrange, Act
82+
error = BentoServerError(
83+
http_status=500,
84+
http_body=None,
85+
message="Internal Server Error",
86+
)
87+
88+
# Assert
89+
assert str(error) == "500 Internal Server Error"
90+
assert (
91+
repr(error)
92+
== "BentoServerError(request_id=None, http_status=500, message=Internal Server Error)" # noqa
93+
)

0 commit comments

Comments
 (0)