Skip to content

Commit 9f26711

Browse files
Centralise error handling
1 parent 08eeb4e commit 9f26711

File tree

2 files changed

+73
-82
lines changed

2 files changed

+73
-82
lines changed

authsignal/client.py

+72-81
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,22 @@ def _remove_none_values(d: Dict[str, Any]) -> Dict[str, Any]:
5151
return {k: v for k, v in d.items() if v is not None}
5252

5353
def send(self, request, **kwargs):
54-
response = super().send(request, **kwargs)
55-
if response.headers.get('Content-Type') == 'application/json':
56-
try:
57-
data = response.json()
58-
if isinstance(data, dict) and 'actionCode' in data:
59-
del data['actionCode']
60-
response._content = json.dumps(humps.decamelize(data)).encode('utf-8')
61-
except json.JSONDecodeError:
62-
pass
63-
return response
54+
try:
55+
response = super().send(request, **kwargs)
56+
response.raise_for_status() # Raises HTTPError for 4xx/5xx responses
57+
58+
if response.headers.get('Content-Type') == 'application/json':
59+
try:
60+
data = response.json()
61+
if isinstance(data, dict) and 'actionCode' in data:
62+
del data['actionCode']
63+
response._content = json.dumps(humps.decamelize(data)).encode('utf-8')
64+
except json.JSONDecodeError:
65+
pass
66+
return response
67+
except requests.exceptions.RequestException as e:
68+
# Handle the exception globally
69+
raise ApiException(str(e), request.url, http_status_code=e.response.status_code if e.response else None) from e
6470

6571
class Client(object):
6672

@@ -111,19 +117,16 @@ def track(self, user_id, action, payload=None, path=None):
111117
params = {}
112118
timeout = self.timeout
113119

114-
try:
115-
response = self.session.post(
116-
path,
117-
data=json.dumps(payload if payload is not None else {}, cls=DecimalEncoder),
118-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
119-
headers=headers,
120-
timeout=timeout,
121-
params=params)
122-
response.raise_for_status()
120+
response = self.session.post(
121+
path,
122+
data=json.dumps(payload if payload is not None else {}, cls=DecimalEncoder),
123+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
124+
headers=headers,
125+
timeout=timeout,
126+
params=params)
127+
response.raise_for_status()
123128

124-
return response.json()
125-
except requests.exceptions.RequestException as e:
126-
raise ApiException(str(e), path) from e
129+
return response.json()
127130

128131
def get_action(self, user_id, action, idempotency_key, path=None):
129132
"""Retrieves the action from authsignal, scoped to the user_id and action
@@ -142,18 +145,15 @@ def get_action(self, user_id, action, idempotency_key, path=None):
142145
params = {}
143146
timeout = self.timeout
144147

145-
try:
146-
response = self.session.get(
147-
path,
148-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
149-
headers=headers,
150-
timeout=timeout,
151-
params=params)
152-
response.raise_for_status()
148+
response = self.session.get(
149+
path,
150+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
151+
headers=headers,
152+
timeout=timeout,
153+
params=params)
154+
response.raise_for_status()
153155

154-
return response.json()
155-
except requests.exceptions.RequestException as e:
156-
raise ApiException(str(e), path) from e
156+
return response.json()
157157

158158
def get_user(self, user_id, redirect_url=None, path=None):
159159
"""Retrieves the user from authsignal, and returns enrolment status, and self service url.
@@ -174,36 +174,30 @@ def get_user(self, user_id, redirect_url=None, path=None):
174174
_assert_non_empty_unicode(redirect_url, 'redirect_url')
175175
params.update({"redirectUrl": redirect_url})
176176

177-
try:
178-
response = self.session.get(
179-
path,
180-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
181-
headers=headers,
182-
timeout=timeout,
183-
params=params)
184-
response.raise_for_status()
185-
186-
return response.json()
187-
except requests.exceptions.RequestException as e:
188-
raise ApiException(str(e), path) from e
177+
response = self.session.get(
178+
path,
179+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
180+
headers=headers,
181+
timeout=timeout,
182+
params=params)
183+
response.raise_for_status()
184+
185+
return response.json()
189186

190187
def delete_user(self, user_id):
191188
_assert_non_empty_unicode(user_id, 'user_id')
192189

193190
path = self._delete_user_url(user_id)
194191
headers = self._default_headers()
195192

196-
try:
197-
response = self.session.delete(
198-
path,
199-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
200-
headers=headers,
201-
timeout=self.timeout
202-
)
203-
response.raise_for_status()
204-
return response.json()
205-
except requests.exceptions.RequestException as e:
206-
raise ApiException(str(e), path) from e
193+
response = self.session.delete(
194+
path,
195+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
196+
headers=headers,
197+
timeout=self.timeout
198+
)
199+
response.raise_for_status()
200+
return response.json()
207201

208202
def delete_authenticator(self, user_id: str, user_authenticator_id: str) -> Dict[str, Any]:
209203
_assert_non_empty_unicode(user_id, 'user_id')
@@ -214,17 +208,14 @@ def delete_authenticator(self, user_id: str, user_authenticator_id: str) -> Dict
214208
path = f'{self.url}/v1/users/{user_id}/authenticators/{user_authenticator_id}'
215209
headers = self._default_headers()
216210

217-
try:
218-
response = self.session.delete(
219-
path,
220-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
221-
headers=headers,
222-
timeout=self.timeout
223-
)
224-
response.raise_for_status()
225-
return response.json()
226-
except requests.exceptions.RequestException as e:
227-
raise ApiException(str(e), path) from e
211+
response = self.session.delete(
212+
path,
213+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
214+
headers=headers,
215+
timeout=self.timeout
216+
)
217+
response.raise_for_status()
218+
return response.json()
228219

229220
def update_user(self, user_id, data):
230221
user_id = urllib.parse.quote(user_id)
@@ -259,18 +250,15 @@ def enroll_verified_authenticator(self, user_id, authenticator_payload, path=No
259250
params = {}
260251
timeout = self.timeout
261252

262-
try:
263-
response = self.session.post(
264-
path,
265-
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
266-
data=json.dumps(authenticator_payload),
267-
headers=headers,
268-
timeout=timeout,
269-
params=params)
270-
response.raise_for_status()
271-
return response.json()
272-
except requests.exceptions.RequestException as e:
273-
raise ApiException(str(e), path) from e
253+
response = self.session.post(
254+
path,
255+
auth=requests.auth.HTTPBasicAuth(self.api_key, ''),
256+
data=json.dumps(authenticator_payload),
257+
headers=headers,
258+
timeout=timeout,
259+
params=params)
260+
response.raise_for_status()
261+
return response.json()
274262

275263
def validate_challenge(self, token: str, user_id: Optional[str] = None, action: Optional[str] = None) -> Dict[str, Any]:
276264
path = self._validate_challenge_url()
@@ -336,15 +324,18 @@ def _ensure_versioned_path(self, path):
336324
class ApiException(Exception):
337325
def __init__(self, message, url, http_status_code=None, body=None, api_status=None,
338326
api_error_message=None, request=None):
339-
Exception.__init__(self, message)
340-
327+
super().__init__(message)
341328
self.url = url
342329
self.http_status_code = http_status_code
343330
self.body = body
344331
self.api_status = api_status
345332
self.api_error_message = api_error_message
346333
self.request = request
347334

335+
def __str__(self):
336+
return (f"{super().__str__()} status: {self.http_status_code}, "
337+
f"error: {self.api_error_message}, description: {self.body}")
338+
348339
def _assert_non_empty_unicode(val, name, error_cls=None):
349340
error = False
350341
if not isinstance(val, _UNICODE_STRING):

authsignal/client_tests.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def test_delete_authenticator(self):
170170
def test_validate_challenge_returns_success_false_if_user_id_is_incorrect(self):
171171
responses.add(responses.POST, f"{base_url}/validate",
172172
json={'isValid': False, 'error': 'User is invalid.'},
173-
status=400
173+
status=200
174174
)
175175

176176
response = self.authsignal_client.validate_challenge(user_id="spoofed_id", token=self.jwt_token)

0 commit comments

Comments
 (0)