394 lines
9.2 KiB
Python
394 lines
9.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
|
||
"""
|
||
oss2.exceptions
|
||
~~~~~~~~~~~~~~
|
||
|
||
异常类。
|
||
"""
|
||
|
||
import re
|
||
import base64
|
||
import xml.etree.ElementTree as ElementTree
|
||
from xml.parsers import expat
|
||
|
||
|
||
from .compat import to_string
|
||
from .headers import *
|
||
|
||
_OSS_ERROR_TO_EXCEPTION = {} # populated at end of module
|
||
|
||
|
||
OSS_CLIENT_ERROR_STATUS = -1
|
||
OSS_REQUEST_ERROR_STATUS = -2
|
||
OSS_INCONSISTENT_ERROR_STATUS = -3
|
||
OSS_FORMAT_ERROR_STATUS = -4
|
||
OSS_SELECT_CLIENT_ERROR_STATUS = -5
|
||
|
||
|
||
class OssError(Exception):
|
||
def __init__(self, status, headers, body, details):
|
||
#: HTTP 状态码
|
||
self.status = status
|
||
|
||
#: 请求ID,用于跟踪一个OSS请求。提交工单时,最好能够提供请求ID
|
||
self.request_id = headers.get(OSS_REQUEST_ID, '')
|
||
|
||
#: HTTP响应体(部分)
|
||
self.body = body
|
||
|
||
#: 详细错误信息,是一个string到string的dict
|
||
self.details = details
|
||
|
||
#: OSS错误码
|
||
self.code = self.details.get('Code', '')
|
||
|
||
#: OSS错误信息
|
||
self.message = self.details.get('Message', '')
|
||
|
||
#: OSS新的错误码
|
||
self.ec = self.details.get('EC', '')
|
||
|
||
#: header信息
|
||
self.headers = headers
|
||
|
||
def __str__(self):
|
||
error = {'status': self.status,
|
||
OSS_REQUEST_ID : self.request_id,
|
||
'details': self.details}
|
||
return str(error)
|
||
|
||
def _str_with_body(self):
|
||
error = {'status': self.status,
|
||
OSS_REQUEST_ID : self.request_id,
|
||
'details': self.body}
|
||
return str(error)
|
||
|
||
|
||
class ClientError(OssError):
|
||
def __init__(self, message):
|
||
OssError.__init__(self, OSS_CLIENT_ERROR_STATUS, {}, 'ClientError: ' + message, {})
|
||
|
||
def __str__(self):
|
||
return self._str_with_body()
|
||
|
||
|
||
class RequestError(OssError):
|
||
def __init__(self, e):
|
||
OssError.__init__(self, OSS_REQUEST_ERROR_STATUS, {}, 'RequestError: ' + str(e), {})
|
||
self.exception = e
|
||
|
||
def __str__(self):
|
||
return self._str_with_body()
|
||
|
||
|
||
class InconsistentError(OssError):
|
||
def __init__(self, message, request_id=''):
|
||
OssError.__init__(self, OSS_INCONSISTENT_ERROR_STATUS, {OSS_REQUEST_ID : request_id}, 'InconsistentError: ' + message, {})
|
||
|
||
def __str__(self):
|
||
return self._str_with_body()
|
||
|
||
|
||
class OpenApiFormatError(OssError):
|
||
def __init__(self, message):
|
||
OssError.__init__(self, OSS_FORMAT_ERROR_STATUS, {}, message, {})
|
||
|
||
def __str__(self):
|
||
return self._str_with_body()
|
||
|
||
|
||
class OpenApiServerError(OssError):
|
||
def __init__(self, status, request_id, message, error_code):
|
||
OssError.__init__(self, status, {OSS_REQUEST_ID : request_id}, '', {'Code': error_code, 'Message': message})
|
||
|
||
|
||
class ServerError(OssError):
|
||
pass
|
||
|
||
|
||
class NotFound(ServerError):
|
||
status = 404
|
||
code = ''
|
||
|
||
|
||
class MalformedXml(ServerError):
|
||
status = 400
|
||
code = 'MalformedXML'
|
||
|
||
|
||
class InvalidRequest(ServerError):
|
||
status = 400
|
||
code = 'InvalidRequest'
|
||
|
||
|
||
class OperationNotSupported(ServerError):
|
||
status = 400
|
||
code = 'OperationNotSupported'
|
||
|
||
|
||
class RestoreAlreadyInProgress(ServerError):
|
||
status = 409
|
||
code = 'RestoreAlreadyInProgress'
|
||
|
||
|
||
class InvalidArgument(ServerError):
|
||
status = 400
|
||
code = 'InvalidArgument'
|
||
|
||
def __init__(self, status, headers, body, details):
|
||
super(InvalidArgument, self).__init__(status, headers, body, details)
|
||
self.name = details.get('ArgumentName')
|
||
self.value = details.get('ArgumentValue')
|
||
|
||
|
||
class InvalidDigest(ServerError):
|
||
status = 400
|
||
code = 'InvalidDigest'
|
||
|
||
|
||
class InvalidObjectName(ServerError):
|
||
status = 400
|
||
code = 'InvalidObjectName'
|
||
|
||
|
||
class NotImplemented(ServerError):
|
||
status = 400
|
||
code = 'NotImplemented'
|
||
|
||
|
||
class InvalidEncryptionRequest(ServerError):
|
||
status = 400
|
||
code = 'InvalidEncryptionRequest'
|
||
|
||
class BucketReplicationAlreadyExist(ServerError):
|
||
status = 400
|
||
code = 'BucketReplicationAlreadyExist'
|
||
|
||
class NoSuchBucket(NotFound):
|
||
status = 404
|
||
code = 'NoSuchBucket'
|
||
|
||
|
||
class NoSuchKey(NotFound):
|
||
status = 404
|
||
code = 'NoSuchKey'
|
||
|
||
|
||
class NoSuchUpload(NotFound):
|
||
status = 404
|
||
code = 'NoSuchUpload'
|
||
|
||
|
||
class NoSuchWebsite(NotFound):
|
||
status = 404
|
||
code = 'NoSuchWebsiteConfiguration'
|
||
|
||
|
||
class NoSuchLifecycle(NotFound):
|
||
status = 404
|
||
code = 'NoSuchLifecycle'
|
||
|
||
|
||
class NoSuchCors(NotFound):
|
||
status = 404
|
||
code = 'NoSuchCORSConfiguration'
|
||
|
||
|
||
class NoSuchLiveChannel(NotFound):
|
||
status = 404
|
||
code = 'NoSuchLiveChannel'
|
||
|
||
|
||
class NoSuchBucketPolicy(NotFound):
|
||
status = 404
|
||
code = 'NoSuchBucketPolicy'
|
||
|
||
class NoSuchInventory(NotFound):
|
||
status = 404
|
||
code = 'NoSuchInventory'
|
||
|
||
class NoSuchReplicationRule(NotFound):
|
||
status = 404
|
||
code = 'NoSuchReplicationRule'
|
||
|
||
class Conflict(ServerError):
|
||
status = 409
|
||
code = ''
|
||
|
||
|
||
class BucketNotEmpty(Conflict):
|
||
status = 409
|
||
code = 'BucketNotEmpty'
|
||
|
||
|
||
class PositionNotEqualToLength(Conflict):
|
||
status = 409
|
||
code = 'PositionNotEqualToLength'
|
||
|
||
def __init__(self, status, headers, body, details):
|
||
super(PositionNotEqualToLength, self).__init__(status, headers, body, details)
|
||
self.next_position = int(headers[OSS_NEXT_APPEND_POSITION])
|
||
|
||
|
||
class ObjectNotAppendable(Conflict):
|
||
status = 409
|
||
code = 'ObjectNotAppendable'
|
||
|
||
|
||
class ChannelStillLive(Conflict):
|
||
status = 409
|
||
code = 'ChannelStillLive'
|
||
|
||
|
||
class LiveChannelDisabled(Conflict):
|
||
status = 409
|
||
code = 'LiveChannelDisabled'
|
||
|
||
|
||
class PreconditionFailed(ServerError):
|
||
status = 412
|
||
code = 'PreconditionFailed'
|
||
|
||
|
||
class NotModified(ServerError):
|
||
status = 304
|
||
code = ''
|
||
|
||
|
||
class AccessDenied(ServerError):
|
||
status = 403
|
||
code = 'AccessDenied'
|
||
|
||
class NoSuchServerSideEncryptionRule(NotFound):
|
||
status = 404
|
||
code = 'NoSuchServerSideEncryptionRule'
|
||
|
||
class InvalidEncryptionAlgorithmError(ServerError):
|
||
status = 400
|
||
code = 'InvalidEncryptionAlgorithmError'
|
||
|
||
class SelectOperationFailed(ServerError):
|
||
code = 'SelectOperationFailed'
|
||
def __init__(self, status, code, message):
|
||
self.status = status
|
||
self.code = code
|
||
self.message = message
|
||
|
||
def __str__(self):
|
||
error = {'status': self.status,
|
||
'code': self.code,
|
||
'details': self.message}
|
||
return str(error)
|
||
|
||
class SelectOperationClientError(OssError):
|
||
def __init__(self, message, request_id):
|
||
OssError.__init__(self, OSS_SELECT_CLIENT_ERROR_STATUS, {'x-oss-request-id': request_id}, 'SelectOperationClientError: ' + message, {})
|
||
|
||
def __str__(self):
|
||
error = {'x-oss-request-id':self.request_id,
|
||
'message': self.message}
|
||
return str(error)
|
||
|
||
class SignatureDoesNotMatch(ServerError):
|
||
status = 403
|
||
code = 'SignatureDoesNotMatch'
|
||
|
||
class ObjectAlreadyExists(ServerError):
|
||
status = 400
|
||
code = 'ObjectAlreadyExists'
|
||
|
||
class PartNotSequential(ServerError):
|
||
status = 400
|
||
code = 'PartNotSequential'
|
||
|
||
class NoSuchWORMConfiguration(ServerError):
|
||
status = 404
|
||
code = 'NoSuchWORMConfiguration'
|
||
|
||
class WORMConfigurationLocked(ServerError):
|
||
status = 403
|
||
code = 'WORMConfigurationLocked'
|
||
|
||
class InvalidWORMConfiguration(ServerError):
|
||
status = 400
|
||
code = 'InvalidWORMConfiguration'
|
||
|
||
class NoSuchTransferAccelerationConfiguration(ServerError):
|
||
status = 404
|
||
code = 'NoSuchTransferAccelerationConfiguration'
|
||
|
||
def make_exception(resp):
|
||
status = resp.status
|
||
headers = resp.headers
|
||
body = resp.read(4096)
|
||
if not body and headers.get('x-oss-err') is not None:
|
||
try:
|
||
value = base64.b64decode(to_string(headers.get('x-oss-err')))
|
||
except:
|
||
value = body
|
||
details = _parse_error_body(value)
|
||
else:
|
||
details = _parse_error_body(body)
|
||
code = details.get('Code', '')
|
||
|
||
try:
|
||
klass = _OSS_ERROR_TO_EXCEPTION[(status, code)]
|
||
return klass(status, headers, body, details)
|
||
except KeyError:
|
||
return ServerError(status, headers, body, details)
|
||
|
||
|
||
def _walk_subclasses(klass):
|
||
for sub in klass.__subclasses__():
|
||
yield sub
|
||
for subsub in _walk_subclasses(sub):
|
||
yield subsub
|
||
|
||
|
||
for klass in _walk_subclasses(ServerError):
|
||
status = getattr(klass, 'status', None)
|
||
code = getattr(klass, 'code', None)
|
||
|
||
if status is not None and code is not None:
|
||
_OSS_ERROR_TO_EXCEPTION[(status, code)] = klass
|
||
|
||
|
||
# XML parsing exceptions have changed in Python2.7 and ElementTree 1.3
|
||
if hasattr(ElementTree, 'ParseError'):
|
||
ElementTreeParseError = (ElementTree.ParseError, expat.ExpatError)
|
||
else:
|
||
ElementTreeParseError = (expat.ExpatError)
|
||
|
||
|
||
def _parse_error_body(body):
|
||
try:
|
||
root = ElementTree.fromstring(body)
|
||
if root.tag != 'Error':
|
||
return {}
|
||
|
||
details = {}
|
||
for child in root:
|
||
details[child.tag] = child.text
|
||
return details
|
||
except ElementTreeParseError:
|
||
return _guess_error_details(body)
|
||
|
||
|
||
def _guess_error_details(body):
|
||
details = {}
|
||
body = to_string(body)
|
||
|
||
if '<Error>' not in body or '</Error>' not in body:
|
||
return details
|
||
|
||
m = re.search('<Code>(.*)</Code>', body)
|
||
if m:
|
||
details['Code'] = m.group(1)
|
||
|
||
m = re.search('<Message>(.*)</Message>', body)
|
||
if m:
|
||
details['Message'] = m.group(1)
|
||
|
||
return details
|