-
Notifications
You must be signed in to change notification settings - Fork 4
[SK-1851] Update Error Handling for Python SDK #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
4649d8e
[SK-1851] Update Error Handling for Python SDK
jranaskit 6cbc4a4
[SK-1851] Update Error Handling for Python SDK
jranaskit 0ef430c
Updated Error Handling to raise different exceptions appropriately wi…
jranaskit 17a0ad7
Updated Structure as per discussion and added WebhookVerification and…
jranaskit 221180c
Fixed Review Comments by Rex
jranaskit e58ff51
Fixed Review Comments by Rex
jranaskit cc3247f
Fixed Review Comments by Rex
jranaskit 7dd50a4
Fixed Review Comments by Rex - 2
jranaskit 507328d
Fixed Review Comments by Copilot
jranaskit 86d4c8c
Fixed Review Comments by Copilot - 2
jranaskit 5e6e849
Fixed Review Comments by Cursor
jranaskit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,260 @@ | ||
|
||
import grpc | ||
from grpc import StatusCode | ||
from http import HTTPStatus | ||
from grpc_status import rpc_status | ||
from requests.models import Response | ||
from scalekit.v1.errdetails.errdetails_pb2 import ErrorInfo | ||
|
||
|
||
GRPC_TO_HTTP = { | ||
StatusCode.OK: HTTPStatus.OK, | ||
StatusCode.INVALID_ARGUMENT: HTTPStatus.BAD_REQUEST, | ||
StatusCode.FAILED_PRECONDITION: HTTPStatus.BAD_REQUEST, | ||
StatusCode.OUT_OF_RANGE: HTTPStatus.BAD_REQUEST, | ||
StatusCode.UNAUTHENTICATED: HTTPStatus.UNAUTHORIZED, | ||
StatusCode.PERMISSION_DENIED: HTTPStatus.FORBIDDEN, | ||
StatusCode.NOT_FOUND: HTTPStatus.NOT_FOUND, | ||
StatusCode.ALREADY_EXISTS: HTTPStatus.CONFLICT, | ||
StatusCode.ABORTED: HTTPStatus.CONFLICT, | ||
StatusCode.RESOURCE_EXHAUSTED: HTTPStatus.TOO_MANY_REQUESTS, | ||
StatusCode.CANCELLED: 499, | ||
StatusCode.DATA_LOSS: HTTPStatus.INTERNAL_SERVER_ERROR, | ||
StatusCode.UNKNOWN: HTTPStatus.INTERNAL_SERVER_ERROR, | ||
StatusCode.INTERNAL: HTTPStatus.INTERNAL_SERVER_ERROR, | ||
StatusCode.UNIMPLEMENTED: HTTPStatus.NOT_IMPLEMENTED, | ||
StatusCode.UNAVAILABLE: HTTPStatus.SERVICE_UNAVAILABLE, | ||
StatusCode.DEADLINE_EXCEEDED: HTTPStatus.GATEWAY_TIMEOUT, | ||
} | ||
|
||
HTTP_TO_GRPC = { | ||
HTTPStatus.OK: StatusCode.OK, | ||
HTTPStatus.BAD_REQUEST: StatusCode.INVALID_ARGUMENT, | ||
HTTPStatus.UNAUTHORIZED: StatusCode.UNAUTHENTICATED, | ||
HTTPStatus.FORBIDDEN: StatusCode.PERMISSION_DENIED, | ||
HTTPStatus.NOT_FOUND: StatusCode.NOT_FOUND, | ||
HTTPStatus.CONFLICT: StatusCode.ALREADY_EXISTS, | ||
HTTPStatus.TOO_MANY_REQUESTS: StatusCode.RESOURCE_EXHAUSTED, | ||
HTTPStatus.INTERNAL_SERVER_ERROR: StatusCode.INTERNAL, | ||
HTTPStatus.NOT_IMPLEMENTED: StatusCode.UNIMPLEMENTED, | ||
HTTPStatus.SERVICE_UNAVAILABLE: StatusCode.UNAVAILABLE, | ||
HTTPStatus.GATEWAY_TIMEOUT: StatusCode.DEADLINE_EXCEEDED, | ||
} | ||
|
||
|
||
HTTP_STATUS = { | ||
'OK': HTTPStatus.OK, | ||
'BAD_REQUEST': HTTPStatus.BAD_REQUEST, | ||
'UNAUTHORIZED': HTTPStatus.UNAUTHORIZED, | ||
'FORBIDDEN': HTTPStatus.FORBIDDEN, | ||
'NOT_FOUND': HTTPStatus.NOT_FOUND, | ||
'CONFLICT': HTTPStatus.CONFLICT, | ||
'TOO_MANY_REQUESTS': HTTPStatus.TOO_MANY_REQUESTS, | ||
'INTERNAL_SERVER_ERROR': HTTPStatus.INTERNAL_SERVER_ERROR, | ||
'NOT_IMPLEMENTED': HTTPStatus.NOT_IMPLEMENTED, | ||
'SERVICE_UNAVAILABLE': HTTPStatus.SERVICE_UNAVAILABLE, | ||
'GATEWAY_TIMEOUT': HTTPStatus.GATEWAY_TIMEOUT, | ||
} | ||
|
||
|
||
class ScalekitException(Exception): | ||
""" Base class for all scalekit exceptions """ | ||
def __init__(self, error): | ||
super().__init__(error) | ||
|
||
|
||
class WebhookVerificationError(ScalekitException): | ||
""" Exception raised for webhook verification failure """ | ||
def __init__(self, error): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitValidateTokenFailureException(ScalekitException): | ||
""" Exception raised for token validation failure """ | ||
def __init__(self, error): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitServerException(ScalekitException): | ||
""" Base class for all scalekit server exceptions """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
self._unpacked_details = list() | ||
if isinstance(error, Response): | ||
if error.reason and isinstance(error.reason, str): | ||
self._http_status = HTTP_STATUS.get(error.reason.upper(), HTTPStatus.INTERNAL_SERVER_ERROR) | ||
else: | ||
self._http_status = HTTP_STATUS.get('INTERNAL_SERVER_ERROR') | ||
self._grpc_status = HTTP_TO_GRPC.get(error.status_code, StatusCode.UNKNOWN) | ||
self._error_code = error.reason | ||
self._err_details = error.text | ||
self._message = None | ||
elif isinstance(error, grpc.RpcError): | ||
self._grpc_status = error.code() | ||
self._http_status = GRPC_TO_HTTP.get(self._grpc_status) | ||
self._message = rpc_status.from_call(error).message | ||
self._err_details = rpc_status.from_call(error).details | ||
self._error_code = None | ||
|
||
for detail in self._err_details: | ||
info = ErrorInfo() | ||
detail.Unpack(info) | ||
self._unpacked_details.append(info) | ||
if not self._error_code: | ||
self._error_code = info.error_code | ||
|
||
@staticmethod | ||
def promote(error: Response | grpc.RpcError): | ||
""" Promote a ScalekitServerException (Response or RpcError) to a specific error type """ | ||
grpc_status = HTTP_TO_GRPC.get(error.status_code) if isinstance(error, Response) else error.code() | ||
|
||
if grpc_status == StatusCode.INVALID_ARGUMENT: | ||
return ScalekitBadRequestException(error) | ||
elif grpc_status == StatusCode.FAILED_PRECONDITION: | ||
return ScalekitBadRequestException(error) | ||
elif grpc_status == StatusCode.OUT_OF_RANGE: | ||
return ScalekitBadRequestException(error) | ||
elif grpc_status == StatusCode.UNAUTHENTICATED: | ||
return ScalekitUnauthorizedException(error) | ||
elif grpc_status == StatusCode.PERMISSION_DENIED: | ||
return ScalekitForbiddenException(error) | ||
elif grpc_status == StatusCode.NOT_FOUND: | ||
return ScalekitNotFoundException(error) | ||
elif grpc_status == StatusCode.ALREADY_EXISTS: | ||
return ScalekitConflictException(error) | ||
elif grpc_status == StatusCode.ABORTED: | ||
return ScalekitConflictException(error) | ||
elif grpc_status == StatusCode.RESOURCE_EXHAUSTED: | ||
return ScalekitTooManyRequestsException(error) | ||
elif grpc_status == StatusCode.CANCELLED: | ||
return ScalekitCancelledException(error) | ||
elif grpc_status == StatusCode.DATA_LOSS: | ||
return ScalekitInternalServerException(error) | ||
elif grpc_status == StatusCode.UNKNOWN: | ||
return ScalekitInternalServerException(error) | ||
elif grpc_status == StatusCode.INTERNAL: | ||
return ScalekitInternalServerException(error) | ||
elif grpc_status == StatusCode.UNIMPLEMENTED: | ||
return ScalekitNotImplementedException(error) | ||
elif grpc_status == StatusCode.UNAVAILABLE: | ||
return ScalekitServiceUnavailableException(error) | ||
elif grpc_status == StatusCode.DEADLINE_EXCEEDED: | ||
return ScalekitGatewayTimeoutException(error) | ||
else: | ||
return ScalekitUnknownException(error) | ||
|
||
jranaskit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def __str__(self): | ||
if self._unpacked_details: | ||
border = "=" * 40 | ||
details_str = str(self._unpacked_details) | ||
if details_str.startswith("[") and "\n" in details_str: | ||
details_str = details_str.replace("[", "[\n", 1) | ||
return (f"\n{border}\n" | ||
f"Error Code: {self._error_code}\n" | ||
f"GRPC: ({self._grpc_status.name}: {self._grpc_status.value})\n" | ||
f"HTTP: ({self._http_status.name}: {self._http_status.value})\n" | ||
f"Error Details:\n" | ||
f"{self._message}: {details_str}\n{border}\n") | ||
else: | ||
border = "=" * 40 | ||
return (f"\n{border}\n" | ||
f"Error Code: {self._error_code}\n" | ||
f"GRPC: ({self._grpc_status.name}: {self._grpc_status.value})\n" | ||
f"HTTP: ({self._http_status.name}: {self._http_status.value})\n" | ||
f"Error Details: {self._err_details}\n{border}\n") | ||
|
||
@property | ||
def http_status(self): | ||
""" Getter for HTTP status code """ | ||
return self._http_status | ||
|
||
@property | ||
def error_code(self): | ||
""" Getter for Error code """ | ||
return self._error_code | ||
jranaskit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@property | ||
def err_details(self): | ||
""" Getter for Error details object """ | ||
return self._err_details | ||
|
||
@property | ||
def grpc_status(self): | ||
""" Getter for GRPC status code """ | ||
return self._grpc_status | ||
|
||
@property | ||
def message(self): | ||
""" Getter for Exception message """ | ||
return self._message | ||
|
||
|
||
class ScalekitBadRequestException(ScalekitServerException): | ||
""" Scalekit Exception raised for bad requests """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitUnauthorizedException(ScalekitServerException): | ||
""" Scalekit Exception raised for unauthorized access """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitForbiddenException(ScalekitServerException): | ||
""" Scalekit Exception raised for forbidden access """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitNotFoundException(ScalekitServerException): | ||
""" Scalekit Exception raised when a resource is not found """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitConflictException(ScalekitServerException): | ||
""" Scalekit Exception raised for conflicts, such as duplicate resources """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitTooManyRequestsException(ScalekitServerException): | ||
""" Scalekit Exception raised when too many requests are made in a short time """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitInternalServerException(ScalekitServerException): | ||
""" Scalekit Exception raised for internal server errors """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitNotImplementedException(ScalekitServerException): | ||
""" Scalekit Exception raised when a feature is not implemented """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitServiceUnavailableException(ScalekitServerException): | ||
""" Scalekit Exception raised when the service is unavailable """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitGatewayTimeoutException(ScalekitServerException): | ||
""" Scalekit Exception raised when a gateway timeout occurs """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitCancelledException(ScalekitServerException): | ||
""" Scalekit Exception raised when an operation is cancelled """ | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) | ||
|
||
|
||
class ScalekitUnknownException(ScalekitServerException): | ||
def __init__(self, error: Response | grpc.RpcError): | ||
super().__init__(error) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.