Skip to content
Draft
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
49 changes: 25 additions & 24 deletions sift/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,41 +61,42 @@ def __init__(self, http_response: requests.Response) -> None:
self.body: dict[str, t.Any] | None = None
self.request: dict[str, t.Any] | None = None

if (
self.http_status_code not in self.HTTP_CODES_WITHOUT_BODY
) and http_response.text:
try:
self.body = http_response.json()
try:
if (
self.http_status_code not in self.HTTP_CODES_WITHOUT_BODY
) and http_response.text:
try:
self.body = http_response.json()

if "status" in self.body:
self.api_status = self.body["status"]
if "status" in self.body:
self.api_status = self.body["status"]

if "error_message" in self.body:
self.api_error_message = self.body["error_message"]
if "error_message" in self.body:
self.api_error_message = self.body["error_message"]

if isinstance(self.body.get("request"), str):
self.request = json.loads(self.body["request"])
except ValueError:
raise ApiException(
f"Failed to parse json response from {self.url}",
url=self.url,
http_status_code=self.http_status_code,
body=self.body,
api_status=self.api_status,
api_error_message=self.api_error_message,
request=self.request,
)
finally:
if not 200 <= self.http_status_code < 300:
if isinstance(self.body.get("request"), str):
self.request = json.loads(self.body["request"])
except ValueError:
raise ApiException(
f"{self.url} returned non-2XX http status code {self.http_status_code}",
f"Failed to parse json response from {self.url}",
url=self.url,
http_status_code=self.http_status_code,
body=self.body,
api_status=self.api_status,
api_error_message=self.api_error_message,
request=self.request,
)
finally:
if not 200 <= self.http_status_code < 300:
raise ApiException(
f"{self.url} returned non-2XX http status code {self.http_status_code}",
url=self.url,
http_status_code=self.http_status_code,
body=self.body,
api_status=self.api_status,
api_error_message=self.api_error_message,
request=self.request,
)

def __str__(self) -> str:
body = (
Expand Down
49 changes: 49 additions & 0 deletions tests/test_empty_error_response.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from __future__ import annotations

from unittest import TestCase, mock

import requests

from sift.client import Client, Response
from sift.exceptions import ApiException


class TestEmptyErrorResponse(TestCase):
def test_empty_and_whitespace_errors_raise(self) -> None:
for status in (400, 401, 429, 500, 503):
for body in (b"", b" "):
with self.subTest(status=status, body=body):
response = requests.Response()
response.status_code = status
response.url = "https://api.sift.com/v205/events"
response._content = body
with self.assertRaises(ApiException) as caught:
Response(response)
self.assertEqual(caught.exception.http_status_code, status)
self.assertEqual(caught.exception.url, response.url)

def test_empty_success_remains_valid(self) -> None:
for status in (200, 201, 204):
with self.subTest(status=status):
response = requests.Response()
response.status_code = status
response.url = "https://api.sift.com/v205/events"
response._content = b""
result = Response(response)
self.assertEqual(result.http_status_code, status)
self.assertIsNone(result.body)

def test_public_track_propagates_empty_server_error(self) -> None:
response = requests.Response()
response.status_code = 503
response.url = "https://api.sift.com/v205/events"
response._content = b""
with requests.Session() as session:
with mock.patch.object(
session, "post", return_value=response
) as post:
client = Client(api_key="test-key", session=session)
with self.assertRaises(ApiException) as caught:
client.track("$create_account", {"$user_id": "test-user"})
post.assert_called_once()
self.assertEqual(caught.exception.http_status_code, 503)