Skip to content

Commit

Permalink
..
Browse files Browse the repository at this point in the history
  • Loading branch information
nfx committed Nov 10, 2023
1 parent 52ff2ea commit 47bd23e
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 73 deletions.
41 changes: 7 additions & 34 deletions .codegen/error_mapping.py.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,21 @@

from .base import DatabricksError

__all__ = ['error_mapper'{{range .ExceptionTypes}}, '{{.PascalName}}'{{end}}]
__all__ = [{{range $i, $v := .ExceptionTypes}}{{if $i}}, {{end}}'{{.PascalName}}'{{end}}]

{{$builtinErrors := dict
"DEADLINE_EXCEEDED" "TimeoutError"
"NOT_FOUND" "LookupError"
"UNAUTHENTICATED" "PermissionError"
"PERMISSION_DENIED" "PermissionError"
"TOO_MANY_REQUESTS" "ResourceWarning"
"NOT_IMPLEMENTED" "NotImplementedError"
}}

{{range .ExceptionTypes}}{{- $builtin := getOrDefault $builtinErrors .Name "" -}}
class {{.PascalName}}({{if .Inherit -}}
{{range .ExceptionTypes}}
class {{.PascalName}}({{if .Inherit -}}
{{.Inherit.PascalName}}
{{- else -}}
DatabricksError
{{- end -}}{{if ne "" $builtin -}}
, {{ $builtin }}
{{- end}}):
"""{{.Comment " " 100 | trimSuffix "\"" }}"""
{{- end -}}):
"""{{.Comment " " 100 | trimSuffix "\"" }}"""
{{end}}

_STATUS_CODE_MAPPING = { {{range .ErrorStatusCodeMapping}}
STATUS_CODE_MAPPING = { {{range .ErrorStatusCodeMapping}}
{{.StatusCode}}: {{.PascalName}},{{- end}}
}

_ERROR_CODE_MAPPING = { {{range .ErrorCodeMapping}}
ERROR_CODE_MAPPING = { {{range .ErrorCodeMapping}}
'{{.ErrorCode}}': {{.PascalName}},{{- end}}
}

def error_mapper(status_code: int, raw: dict) -> DatabricksError:
error_code = raw.get('error_code', None)
if error_code in _ERROR_CODE_MAPPING:
# more specific error codes override more generic HTTP status codes
return _ERROR_CODE_MAPPING[error_code](**raw)

if status_code in _STATUS_CODE_MAPPING:
# more generic HTTP status codes matched after more specific error codes,
# where there's a default exception class per HTTP status code, and we do
# rely on Databricks platform exception mapper to do the right thing.
return _STATUS_CODE_MAPPING[status_code](**raw)

# backwards-compatible error creation for cases like using older versions of
# the SDK on way never releases of the platform.
return DatabricksError(**raw)
1 change: 1 addition & 0 deletions databricks/sdk/errors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .base import DatabricksError, ErrorDetail
from .mapper import error_mapper
from .mapping import *
from .sdk import *
19 changes: 19 additions & 0 deletions databricks/sdk/errors/mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from databricks.sdk.errors import mapping
from databricks.sdk.errors.base import DatabricksError


def error_mapper(status_code: int, raw: dict) -> DatabricksError:
error_code = raw.get('error_code', None)
if error_code in mapping.ERROR_CODE_MAPPING:
# more specific error codes override more generic HTTP status codes
return mapping.ERROR_CODE_MAPPING[error_code](**raw)

if status_code in mapping.STATUS_CODE_MAPPING:
# more generic HTTP status codes matched after more specific error codes,
# where there's a default exception class per HTTP status code, and we do
# rely on Databricks platform exception mapper to do the right thing.
return mapping.STATUS_CODE_MAPPING[status_code](**raw)

# backwards-compatible error creation for cases like using older versions of
# the SDK on way never releases of the platform.
return DatabricksError(**raw)
41 changes: 12 additions & 29 deletions databricks/sdk/errors/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,34 @@
from .base import DatabricksError

__all__ = [
'error_mapper', 'BadRequest', 'Unauthenticated', 'PermissionDenied', 'NotFound', 'ResourceConflict',
'TooManyRequests', 'Cancelled', 'InternalError', 'NotImplemented', 'TemporarilyUnavailable',
'DeadlineExceeded', 'InvalidParameterValue', 'Aborted', 'AlreadyExists', 'ResourceAlreadyExists',
'ResourceExhausted', 'RequestLimitExceeded', 'Unknown', 'DataLoss'
'BadRequest', 'Unauthenticated', 'PermissionDenied', 'NotFound', 'ResourceConflict', 'TooManyRequests',
'Cancelled', 'InternalError', 'NotImplemented', 'TemporarilyUnavailable', 'DeadlineExceeded',
'InvalidParameterValue', 'Aborted', 'AlreadyExists', 'ResourceAlreadyExists', 'ResourceExhausted',
'RequestLimitExceeded', 'Unknown', 'DataLoss'
]


class BadRequest(DatabricksError):
"""the request is invalid"""


class Unauthenticated(DatabricksError, PermissionError):
class Unauthenticated(DatabricksError):
"""the request does not have valid authentication (AuthN) credentials for the operation"""


class PermissionDenied(DatabricksError, PermissionError):
class PermissionDenied(DatabricksError):
"""the caller does not have permission to execute the specified operation"""


class NotFound(DatabricksError, LookupError):
class NotFound(DatabricksError):
"""the operation was performed on a resource that does not exist"""


class ResourceConflict(DatabricksError):
"""maps to all HTTP 409 (Conflict) responses"""


class TooManyRequests(DatabricksError, ResourceWarning):
class TooManyRequests(DatabricksError):
"""maps to HTTP code: 429 Too Many Requests"""


Expand All @@ -42,15 +42,15 @@ class InternalError(DatabricksError):
"""some invariants expected by the underlying system have been broken"""


class NotImplemented(DatabricksError, NotImplementedError):
class NotImplemented(DatabricksError):
"""the operation is not implemented or is not supported/enabled in this service"""


class TemporarilyUnavailable(DatabricksError):
"""the service is currently unavailable"""


class DeadlineExceeded(DatabricksError, TimeoutError):
class DeadlineExceeded(DatabricksError):
"""the deadline expired before the operation could complete"""


Expand Down Expand Up @@ -87,7 +87,7 @@ class DataLoss(InternalError):
"""unrecoverable data loss or corruption"""


_STATUS_CODE_MAPPING = {
STATUS_CODE_MAPPING = {
400: BadRequest,
401: Unauthenticated,
403: PermissionDenied,
Expand All @@ -101,7 +101,7 @@ class DataLoss(InternalError):
504: DeadlineExceeded,
}

_ERROR_CODE_MAPPING = {
ERROR_CODE_MAPPING = {
'INVALID_PARAMETER_VALUE': InvalidParameterValue,
'ABORTED': Aborted,
'ALREADY_EXISTS': AlreadyExists,
Expand All @@ -111,20 +111,3 @@ class DataLoss(InternalError):
'UNKNOWN': Unknown,
'DATA_LOSS': DataLoss,
}


def error_mapper(status_code: int, raw: dict) -> DatabricksError:
error_code = raw.get('error_code', None)
if error_code in _ERROR_CODE_MAPPING:
# more specific error codes override more generic HTTP status codes
return _ERROR_CODE_MAPPING[error_code](**raw)

if status_code in _STATUS_CODE_MAPPING:
# more generic HTTP status codes matched after more specific error codes,
# where there's a default exception class per HTTP status code, and we do
# rely on Databricks platform exception mapper to do the right thing.
return _STATUS_CODE_MAPPING[status_code](**raw)

# backwards-compatible error creation for cases like using older versions of
# the SDK on way never releases of the platform.
return DatabricksError(**raw)
18 changes: 8 additions & 10 deletions tests/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,24 @@ def test_missing_error_code():
[(400, ..., errors.BadRequest), (400, 'INVALID_PARAMETER_VALUE', errors.BadRequest),
(400, 'INVALID_PARAMETER_VALUE', errors.InvalidParameterValue),
(400, 'REQUEST_LIMIT_EXCEEDED', errors.TooManyRequests), (400, ..., IOError),
(401, ..., errors.Unauthenticated), (401, ..., PermissionError),
(401, ..., IOError), (403, ..., errors.PermissionDenied),
(403, ..., PermissionError), (403, ..., IOError), (404, ..., errors.NotFound),
(404, ..., LookupError), (404, ..., IOError), (409, ..., errors.ResourceConflict),
(409, 'ABORTED', errors.Aborted), (409, 'ABORTED', errors.ResourceConflict),
(401, ..., errors.Unauthenticated), (401, ..., IOError),
(403, ..., errors.PermissionDenied),
(403, ..., IOError), (404, ..., errors.NotFound), (404, ..., IOError),
(409, ..., errors.ResourceConflict), (409, 'ABORTED', errors.Aborted),
(409, 'ABORTED', errors.ResourceConflict),
(409, 'ALREADY_EXISTS', errors.AlreadyExists),
(409, 'ALREADY_EXISTS', errors.ResourceConflict), (409, ..., IOError),
(429, ..., errors.TooManyRequests),
(429, 'REQUEST_LIMIT_EXCEEDED', errors.TooManyRequests),
(429, 'REQUEST_LIMIT_EXCEEDED', errors.RequestLimitExceeded),
(429, 'RESOURCE_EXHAUSTED', errors.TooManyRequests),
(429, 'RESOURCE_EXHAUSTED', errors.ResourceExhausted), (429, ..., ResourceWarning),
(417, 'REQUEST_LIMIT_EXCEEDED', ResourceWarning), (429, ..., IOError),
(429, 'RESOURCE_EXHAUSTED', errors.ResourceExhausted), (429, ..., IOError),
(499, ..., errors.Cancelled), (499, ..., IOError), (500, ..., errors.InternalError),
(500, 'UNKNOWN', errors.InternalError), (500, 'UNKNOWN', errors.Unknown),
(500, 'DATA_LOSS', errors.InternalError), (500, 'DATA_LOSS', errors.DataLoss),
(500, ..., IOError), (501, ..., errors.NotImplemented),
(501, ..., NotImplementedError), (501, ..., IOError),
(500, ..., IOError), (501, ..., errors.NotImplemented), (501, ..., IOError),
(503, ..., errors.TemporarilyUnavailable), (503, ..., IOError),
(504, ..., errors.DeadlineExceeded), (504, ..., TimeoutError), (504, ..., IOError),
(504, ..., errors.DeadlineExceeded), (504, ..., IOError),
(444, ..., errors.DatabricksError), (444, ..., IOError), ])
def test_subclasses(status_code, error_code, klass):
try:
Expand Down

0 comments on commit 47bd23e

Please sign in to comment.