Skip to content

Commit bdd427d

Browse files
Allow2CEOruvnet
andcommitted
Add exception classes
Allow2Error (base), ApiError (with http_status_code and response_body), TokenExpiredError (with user_id), UnpairedError (with user_id) -- matching PHP exception hierarchy. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent 3c4d87f commit bdd427d

5 files changed

Lines changed: 129 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Allow2 SDK exceptions."""
2+
3+
from allow2_service.exceptions.base import Allow2Error
4+
from allow2_service.exceptions.api_error import ApiError
5+
from allow2_service.exceptions.token_expired import TokenExpiredError
6+
from allow2_service.exceptions.unpaired import UnpairedError
7+
8+
__all__ = [
9+
"Allow2Error",
10+
"ApiError",
11+
"TokenExpiredError",
12+
"UnpairedError",
13+
]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Thrown when an Allow2 API call fails with an unexpected error."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any, Dict, Optional
6+
7+
from allow2_service.exceptions.base import Allow2Error
8+
9+
10+
class ApiError(Allow2Error):
11+
"""Thrown when an Allow2 API call fails with an unexpected error.
12+
13+
Args:
14+
message: Human-readable error description.
15+
http_status_code: The HTTP status code from the response.
16+
response_body: The decoded JSON response body, if available.
17+
"""
18+
19+
def __init__(
20+
self,
21+
message: str,
22+
http_status_code: int,
23+
response_body: Optional[Dict[str, Any]] = None,
24+
) -> None:
25+
super().__init__(
26+
message,
27+
context={
28+
"http_status_code": http_status_code,
29+
"response_body": response_body,
30+
},
31+
)
32+
self.http_status_code = http_status_code
33+
self.response_body = response_body

allow2_service/exceptions/base.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Base exception for all Allow2 SDK errors."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any, Dict, Optional
6+
7+
8+
class Allow2Error(RuntimeError):
9+
"""Base exception for all Allow2 SDK errors.
10+
11+
Args:
12+
message: Human-readable error description.
13+
context: Optional dictionary of additional context.
14+
"""
15+
16+
def __init__(
17+
self,
18+
message: str = "",
19+
context: Optional[Dict[str, Any]] = None,
20+
) -> None:
21+
super().__init__(message)
22+
self.context: Dict[str, Any] = context or {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Thrown when the OAuth2 token refresh fails and no valid token is available."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Optional
6+
7+
from allow2_service.exceptions.base import Allow2Error
8+
9+
10+
class TokenExpiredError(Allow2Error):
11+
"""Thrown when the OAuth2 token refresh fails and no valid token is available.
12+
13+
The integration should redirect the user through OAuth2 re-pairing.
14+
15+
Args:
16+
user_id: The integrating application's user ID.
17+
message: Human-readable error description.
18+
"""
19+
20+
def __init__(
21+
self,
22+
user_id: str,
23+
message: str = "OAuth2 token refresh failed. Re-authorization required.",
24+
previous: Optional[BaseException] = None,
25+
) -> None:
26+
super().__init__(message, context={"user_id": user_id})
27+
self.user_id = user_id
28+
if previous is not None:
29+
self.__cause__ = previous
30+
31+
def get_user_id(self) -> str:
32+
"""Return the user ID associated with this error."""
33+
return self.user_id
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""Thrown when the service account is no longer linked (HTTP 401/403 from API)."""
2+
3+
from __future__ import annotations
4+
5+
from allow2_service.exceptions.base import Allow2Error
6+
7+
8+
class UnpairedError(Allow2Error):
9+
"""Thrown when the service account is no longer linked (HTTP 401/403 from API).
10+
11+
The integration should redirect the user through OAuth2 re-pairing.
12+
13+
Args:
14+
user_id: The integrating application's user ID.
15+
message: Human-readable error description.
16+
"""
17+
18+
def __init__(
19+
self,
20+
user_id: str,
21+
message: str = "Service account is no longer linked. Re-pairing required.",
22+
) -> None:
23+
super().__init__(message, context={"user_id": user_id})
24+
self.user_id = user_id
25+
26+
def get_user_id(self) -> str:
27+
"""Return the user ID associated with this error."""
28+
return self.user_id

0 commit comments

Comments
 (0)