Skip to content

Commit fed9dfc

Browse files
Replace base exception field with specific EmailVerificationRequiredException
- Created EmailVerificationRequiredException subclass of AuthorizationException - Added email_verification_id field specific to this exception type - Updated HTTP client to detect email_verification_required error code and raise specific exception - Added tests for both the new exception and to verify regular AuthorizationException still works - All 370 tests pass Co-Authored-By: Deep Singhvi <[email protected]>
1 parent 035b423 commit fed9dfc

File tree

3 files changed

+51
-5
lines changed

3 files changed

+51
-5
lines changed

tests/test_sync_http_client.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
BadRequestException,
1111
BaseRequestException,
1212
ConflictException,
13+
EmailVerificationRequiredException,
1314
ServerException,
1415
)
1516
from workos.utils.http_client import SyncHTTPClient
@@ -263,7 +264,7 @@ def test_conflict_exception(self):
263264
assert str(ex) == "(message=No message, request_id=request-123)"
264265
assert ex.__class__ == ConflictException
265266

266-
def test_authorization_exception_includes_email_verification_id(self):
267+
def test_email_verification_required_exception(self):
267268
request_id = "request-123"
268269
email_verification_id = "email_verification_01J6K4PMSWQXVFGF5ZQJXC6VC8"
269270

@@ -281,14 +282,38 @@ def test_authorization_exception_includes_email_verification_id(self):
281282

282283
try:
283284
self.http_client.request("bad_place")
284-
except AuthorizationException as ex:
285+
except EmailVerificationRequiredException as ex:
285286
assert (
286287
ex.message == "Please verify your email to authenticate via password."
287288
)
288289
assert ex.code == "email_verification_required"
289290
assert ex.email_verification_id == email_verification_id
290291
assert ex.request_id == request_id
292+
assert ex.__class__ == EmailVerificationRequiredException
293+
assert isinstance(ex, AuthorizationException)
294+
295+
def test_regular_authorization_exception_still_raised(self):
296+
request_id = "request-123"
297+
298+
self.http_client._client.request = MagicMock(
299+
return_value=httpx.Response(
300+
status_code=403,
301+
json={
302+
"message": "You do not have permission to access this resource.",
303+
"code": "forbidden",
304+
},
305+
headers={"X-Request-ID": request_id},
306+
),
307+
)
308+
309+
try:
310+
self.http_client.request("bad_place")
311+
except AuthorizationException as ex:
312+
assert ex.message == "You do not have permission to access this resource."
313+
assert ex.code == "forbidden"
314+
assert ex.request_id == request_id
291315
assert ex.__class__ == AuthorizationException
316+
assert not isinstance(ex, EmailVerificationRequiredException)
292317

293318
def test_request_includes_base_headers(self, capture_and_mock_http_client_request):
294319
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)

workos/exceptions.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ def __init__(
2020
self.errors = self.extract_from_json("errors", None)
2121
self.code = self.extract_from_json("code", None)
2222
self.error_description = self.extract_from_json("error_description", "Unknown")
23-
self.email_verification_id = self.extract_from_json(
24-
"email_verification_id", None
25-
)
2623

2724
self.request_id = response.headers.get("X-Request-ID")
2825

@@ -48,6 +45,24 @@ class AuthorizationException(BaseRequestException):
4845
pass
4946

5047

48+
class EmailVerificationRequiredException(AuthorizationException):
49+
"""Raised when email verification is required before authentication.
50+
51+
This exception includes an email_verification_id field that can be used
52+
to retrieve the email verification object or resend the verification email.
53+
"""
54+
55+
def __init__(
56+
self,
57+
response: httpx.Response,
58+
response_json: Optional[Mapping[str, Any]],
59+
) -> None:
60+
super().__init__(response, response_json)
61+
self.email_verification_id = self.extract_from_json(
62+
"email_verification_id", None
63+
)
64+
65+
5166
class AuthenticationException(BaseRequestException):
5267
pass
5368

workos/utils/_base_http_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
ServerException,
2121
AuthenticationException,
2222
AuthorizationException,
23+
EmailVerificationRequiredException,
2324
NotFoundException,
2425
BadRequestException,
2526
)
@@ -99,6 +100,11 @@ def _maybe_raise_error_by_status_code(
99100
if status_code == 401:
100101
raise AuthenticationException(response, response_json)
101102
elif status_code == 403:
103+
if (
104+
response_json is not None
105+
and response_json.get("code") == "email_verification_required"
106+
):
107+
raise EmailVerificationRequiredException(response, response_json)
102108
raise AuthorizationException(response, response_json)
103109
elif status_code == 404:
104110
raise NotFoundException(response, response_json)

0 commit comments

Comments
 (0)