Skip to content

Commit

Permalink
Merge pull request #5 from GoodRx/exceptions-trickle-up
Browse files Browse the repository at this point in the history
Trickle up fatal braze errors as exceptions
  • Loading branch information
pramttl authored Jan 25, 2019
2 parents fea1045 + 4408c52 commit 98d220e
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 39 deletions.
48 changes: 28 additions & 20 deletions braze/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import time

import requests
from requests.exceptions import RequestException
from tenacity import retry
from tenacity import stop_after_attempt
from tenacity import wait_random_exponential
Expand All @@ -22,10 +21,25 @@ def __init__(self, reset_epoch_s):
:param float reset_epoch_s: Unix timestamp for when the API may be called again.
"""
self.reset_epoch_s = reset_epoch_s
super(BrazeRateLimitError, self).__init__("BrazeRateLimitError")
super(BrazeRateLimitError, self).__init__()


class BrazeInternalServerError(Exception):
class BrazeClientError(Exception):
"""
Represents any Braze Fatal Error.
https://www.braze.com/docs/developer_guide/rest_api/user_data/#user-track-responses
"""

pass


class BrazeInternalServerError(BrazeClientError):
"""
Used for Braze API responses where response code is of type 5XX suggesting
Braze side server errors.
"""

pass


Expand Down Expand Up @@ -131,24 +145,18 @@ def __create_request(self, payload):
payload["api_key"] = self.api_key

response = {"errors": []}
try:
r = self._post_request_with_retries(payload)
response.update(r.json())
response["status_code"] = r.status_code

message = response["message"]
if message == "success" or message == "queued":
if not response["errors"]:
response["success"] = True
else:
# Non-Fatal errors
pass
r = self._post_request_with_retries(payload)
response.update(r.json())
response["status_code"] = r.status_code

except (RequestException, BrazeRateLimitError, BrazeInternalServerError) as e:
response["errors"].append(str(e))
message = response["message"]
response["success"] = (
message in ("success", "queued") and not response["errors"]
)

if "success" not in response:
response["success"] = False
if message != "success":
# message contains the fatal error message from Braze
raise BrazeClientError(message, response["errors"])

if "status_code" not in response:
response["status_code"] = 0
Expand All @@ -174,5 +182,5 @@ def _post_request_with_retries(self, payload):
reset_epoch_s = float(r.headers.get("X-RateLimit-Reset", 0))
raise BrazeRateLimitError(reset_epoch_s)
elif str(r.status_code).startswith("5"):
raise BrazeInternalServerError("BrazeInternalServerError")
raise BrazeInternalServerError
return r
38 changes: 19 additions & 19 deletions tests/braze/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from braze.client import _wait_random_exp_or_rate_limit
from braze.client import BrazeClient
from braze.client import BrazeClientError
from braze.client import BrazeInternalServerError
from braze.client import BrazeRateLimitError
from braze.client import MAX_RETRIES
from braze.client import MAX_WAIT_SECONDS
Expand Down Expand Up @@ -93,22 +95,20 @@ def test_user_track(
def test_user_track_request_exception(
self, braze_client, mocker, attributes, events, purchases
):
error_msg = "RequestException Error Message"
mocker.patch.object(
BrazeClient,
"_post_request_with_retries",
side_effect=RequestException(error_msg),
BrazeClient, "_post_request_with_retries", side_effect=RequestException
)

response = braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)
with pytest.raises(RequestException):
braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)

assert braze_client.api_url + "/users/track" == braze_client.request_url
assert response["status_code"] == 0
assert error_msg in response["errors"]

@pytest.mark.parametrize(
"status_code, retry_attempts", [(500, MAX_RETRIES), (401, 1)]
"status_code, retry_attempts, error",
[(500, MAX_RETRIES, BrazeInternalServerError), (401, 1, BrazeClientError)],
)
def test_retries_for_errors(
self,
Expand All @@ -119,6 +119,7 @@ def test_retries_for_errors(
attributes,
events,
purchases,
error,
):
headers = {"Content-Type": "application/json"}
error_msg = "Internal Server Error"
Expand All @@ -127,13 +128,13 @@ def test_retries_for_errors(
ANY, json=mock_json, status_code=status_code, headers=headers
)

response = braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)
with pytest.raises(error):
braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)

stats = braze_client._post_request_with_retries.retry.statistics
assert stats["attempt_number"] == retry_attempts
assert response["success"] is False

@freeze_time()
@pytest.mark.parametrize(
Expand All @@ -159,14 +160,13 @@ def test_retries_for_rate_limit_errors(
mock_json = {"message": error_msg, "errors": error_msg}
requests_mock.post(ANY, json=mock_json, status_code=429, headers=headers)

response = braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)
with pytest.raises(BrazeRateLimitError):
braze_client.user_track(
attributes=attributes, events=events, purchases=purchases
)

stats = braze_client._post_request_with_retries.retry.statistics
assert stats["attempt_number"] == expected_attempts
assert response["success"] is False
assert "BrazeRateLimitError" in response["errors"]

# Ensure the correct wait time is used when rate limited
for i in range(expected_attempts - 1):
Expand Down

0 comments on commit 98d220e

Please sign in to comment.