Skip to content

Commit 0da1595

Browse files
committed
update 3.24.6
1 parent c58bc2e commit 0da1595

File tree

9 files changed

+730
-105
lines changed

9 files changed

+730
-105
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
Version 3.24.6
2+
3+
New Features:
4+
5+
1. Supported check data integrity by crc64, including ObsClient.putFile,ObsClient.putContent,ObsClient.appendObject,ObsClient.uploadPart,ObsClient.completeMultipartUpload.
6+
7+
-------------------------------------------------------------------------------------------------
18
Version 3.24.3
29

310
New Features:

README_CN.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
Version 3.24.3
1+
Version 3.24.6
2+
3+
新特性:
4+
5+
1. 支持crc64校验(ObsClient.putFile/ObsClient.putContent/ObsClient.appendObject/ObsClient.uploadPart/ObsClient.completeMultipartUpload)
6+
7+
-------------------------------------------------------------------------------------------------
8+
Version 3.24.3
29

310
新特性:
411

src/obs/client.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,7 +1692,7 @@ def appendObject(self, bucketName, objectKey, content=None, metadata=None, heade
16921692
headers, readable, notifier, entity = self._prepare_file_notifier_and_entity(offset, file_size, headers,
16931693
progressCallback, file_path,
16941694
readable)
1695-
headers = self.convertor.trans_put_object(metadata=metadata, headers=headers)
1695+
headers = self.convertor.trans_put_object(metadata=metadata, headers=headers, file_path=file_path)
16961696
self.log_client.log(DEBUG, 'send Path:%s' % file_path)
16971697
else:
16981698
entity = content.get('content')
@@ -1701,7 +1701,7 @@ def appendObject(self, bucketName, objectKey, content=None, metadata=None, heade
17011701
autoClose, readable,
17021702
chunkedMode, notifier)
17031703

1704-
headers = self.convertor.trans_put_object(metadata=metadata, headers=headers)
1704+
headers = self.convertor.trans_put_object(metadata=metadata, headers=headers, content=content.get('content'))
17051705

17061706
try:
17071707
if notifier is not None:
@@ -1727,7 +1727,7 @@ def putContent(self, bucketName, objectKey, content=None, metadata=None, headers
17271727
headers = PutObjectHeader()
17281728
if headers.get('contentType') is None:
17291729
headers['contentType'] = const.MIME_TYPES.get(objectKey[objectKey.rfind('.') + 1:].lower())
1730-
_headers = self.convertor.trans_put_object(metadata=metadata, headers=headers)
1730+
_headers = self.convertor.trans_put_object(metadata=metadata, headers=headers, content=content)
17311731

17321732
readable = False
17331733
chunkedMode = False
@@ -1789,12 +1789,12 @@ def putFile(self, bucketName, objectKey, file_path, metadata=None, headers=None,
17891789
__file_path = os.path.join(file_path, f)
17901790
if not const.IS_PYTHON2:
17911791
if not objectKey:
1792-
key = util.safe_trans_to_gb2312('{0}/'.format(os.path.split(file_path)[1]) + f)
1792+
key = util.safe_trans_to_gb2312('{0}'.format(os.path.split(file_path)[1]) + f)
17931793
else:
17941794
key = '{0}/'.format(objectKey) + util.safe_trans_to_gb2312(f)
17951795
else:
17961796
if not objectKey:
1797-
key = util.safe_trans_to_gb2312('{0}/'.format(os.path.split(file_path)[1]) + f).decode(
1797+
key = util.safe_trans_to_gb2312('{0}'.format(os.path.split(file_path)[1]) + f).decode(
17981798
'GB2312').encode('UTF-8')
17991799
else:
18001800
key = '{0}/'.format(objectKey) + util.safe_trans_to_gb2312(f).decode('GB2312').encode('UTF-8')
@@ -1810,7 +1810,7 @@ def putFile(self, bucketName, objectKey, file_path, metadata=None, headers=None,
18101810

18111811
headers = self._putFileHandleHeader(headers, size, objectKey, file_path)
18121812

1813-
_headers = self.convertor.trans_put_object(metadata=metadata, headers=headers)
1813+
_headers = self.convertor.trans_put_object(metadata=metadata, headers=headers, file_path=file_path)
18141814
if const.CONTENT_LENGTH_HEADER not in _headers:
18151815
_headers[const.CONTENT_LENGTH_HEADER] = util.to_string(size)
18161816
self.log_client.log(DEBUG, 'send Path:%s' % file_path)
@@ -1857,13 +1857,16 @@ def _get_part_size(partSize, file_size, offset):
18571857
partSize = partSize if partSize is not None and 0 < partSize <= (file_size - offset) else file_size - offset
18581858
return partSize
18591859

1860-
def _prepare_headers(self, md5, isAttachMd5, file_path, partSize, offset, sseHeader, headers):
1860+
def _prepare_headers(self, md5, isAttachMd5, crc64, isAttachCrc64, file_path, partSize, offset, sseHeader, headers):
18611861
if md5:
18621862
headers[const.CONTENT_MD5_HEADER] = md5
18631863
elif isAttachMd5:
18641864
headers[const.CONTENT_MD5_HEADER] = util.base64_encode(
18651865
util.md5_file_encode_by_size_offset(file_path, partSize, offset, self.chunk_size))
1866-
1866+
if crc64:
1867+
self.convertor._put_key_value(headers, self.ha.crc64_header(), crc64)
1868+
elif isAttachCrc64:
1869+
self.convertor._put_key_value(headers, self.ha.crc64_header(), util.calculate_file_crc64(file_path, offset=offset, totalCount=partSize))
18671870
if sseHeader is not None:
18681871
self.convertor._set_sse_header(sseHeader, headers, True)
18691872

@@ -1879,12 +1882,15 @@ def _prepare_upload_part_notifier(partSize, progressCallback, readable):
18791882

18801883
return readable, notifier
18811884

1882-
def _get_headers(self, md5, sseHeader, headers):
1885+
def _get_headers(self, md5, crc64, isAttachCrc64, content, sseHeader, headers):
18831886
if md5:
18841887
headers[const.CONTENT_MD5_HEADER] = md5
18851888
if sseHeader is not None:
18861889
self.convertor._set_sse_header(sseHeader, headers, True)
1887-
1890+
if crc64:
1891+
headers[self.ha.crc64_header()] = crc64
1892+
elif isAttachCrc64:
1893+
headers[self.ha.crc64_header()] = util.calculate_content_crc64(util.covert_string_to_bytes(content))
18881894
return headers
18891895

18901896
@staticmethod
@@ -1911,7 +1917,7 @@ def _check_file_part_info(self, file_path, offset, partSize):
19111917
@funcCache
19121918
def uploadPart(self, bucketName, objectKey, partNumber, uploadId, object=None, isFile=False, partSize=None,
19131919
offset=0, sseHeader=None, isAttachMd5=False, md5=None, content=None, progressCallback=None,
1914-
autoClose=True, extensionHeaders=None):
1920+
autoClose=True, isAttachCrc64=False, crc64=None, extensionHeaders=None):
19151921
self._assert_not_null(partNumber, 'partNumber is empty')
19161922
self._assert_not_null(uploadId, 'uploadId is empty')
19171923

@@ -1926,7 +1932,7 @@ def uploadPart(self, bucketName, objectKey, partNumber, uploadId, object=None, i
19261932
checked_file_part_info = self._check_file_part_info(content, offset, partSize)
19271933

19281934
headers = {const.CONTENT_LENGTH_HEADER: util.to_string(checked_file_part_info["partSize"])}
1929-
headers = self._prepare_headers(md5, isAttachMd5, checked_file_part_info["file_path"],
1935+
headers = self._prepare_headers(md5, isAttachMd5, crc64, isAttachCrc64, checked_file_part_info["file_path"],
19301936
checked_file_part_info["partSize"], checked_file_part_info["offset"],
19311937
sseHeader, headers)
19321938

@@ -1938,7 +1944,7 @@ def uploadPart(self, bucketName, objectKey, partNumber, uploadId, object=None, i
19381944
headers = {}
19391945
if content is not None and hasattr(content, 'read') and callable(content.read):
19401946
readable = True
1941-
headers = self._get_headers(md5, sseHeader, headers)
1947+
headers = self._get_headers(md5, crc64, isAttachCrc64, content, sseHeader, headers)
19421948

19431949
if partSize is None:
19441950
self.log_client.log(DEBUG, 'missing partSize when uploading a readable stream')
@@ -1956,7 +1962,7 @@ def uploadPart(self, bucketName, objectKey, partNumber, uploadId, object=None, i
19561962
entity = content
19571963
if entity is None:
19581964
entity = ''
1959-
headers = self._get_headers(md5, sseHeader, headers)
1965+
headers = self._get_headers(md5, crc64, isAttachCrc64, content, sseHeader, headers)
19601966

19611967
try:
19621968
if notifier is not None:
@@ -1982,7 +1988,7 @@ def check_file_path(file_path):
19821988
@funcCache
19831989
def _uploadPartWithNotifier(self, bucketName, objectKey, partNumber, uploadId, content=None, isFile=False,
19841990
partSize=None, offset=0, sseHeader=None, isAttachMd5=False, md5=None, notifier=None,
1985-
extensionHeaders=None, headers=None):
1991+
extensionHeaders=None, headers=None, isAttachCrc64=False, crc64=None):
19861992
self._assert_not_null(partNumber, 'partNumber is empty')
19871993
self._assert_not_null(uploadId, 'uploadId is empty')
19881994

@@ -1994,7 +2000,7 @@ def _uploadPartWithNotifier(self, bucketName, objectKey, partNumber, uploadId, c
19942000
checked_file_part_info = self._check_file_part_info(content, offset, partSize)
19952001

19962002
headers[const.CONTENT_LENGTH_HEADER] = util.to_string(checked_file_part_info["partSize"])
1997-
headers = self._prepare_headers(md5, isAttachMd5, checked_file_part_info["file_path"],
2003+
headers = self._prepare_headers(md5, isAttachMd5, crc64, isAttachCrc64, checked_file_part_info["file_path"],
19982004
checked_file_part_info["partSize"], checked_file_part_info["offset"],
19992005
sseHeader, headers)
20002006

@@ -2005,7 +2011,7 @@ def _uploadPartWithNotifier(self, bucketName, objectKey, partNumber, uploadId, c
20052011
else:
20062012
if content is not None and hasattr(content, 'read') and callable(content.read):
20072013
readable = True
2008-
headers = self._get_headers(md5, sseHeader, headers)
2014+
headers = self._get_headers(md5, crc64, isAttachCrc64, content, sseHeader, headers)
20092015

20102016
if partSize is None:
20112017
chunkedMode = True
@@ -2018,7 +2024,7 @@ def _uploadPartWithNotifier(self, bucketName, objectKey, partNumber, uploadId, c
20182024
entity = content
20192025
if entity is None:
20202026
entity = ''
2021-
headers = self._get_headers(md5, sseHeader, headers)
2027+
headers = self._get_headers(md5, crc64, isAttachCrc64, content, sseHeader, headers)
20222028

20232029
ret = self._make_put_request(bucketName, objectKey, pathArgs={'partNumber': partNumber, 'uploadId': uploadId},
20242030
headers=headers, entity=entity, chunkedMode=chunkedMode, methodName='uploadPart',
@@ -2132,16 +2138,16 @@ def copyPart(self, bucketName, objectKey, partNumber, uploadId, copySource, copy
21322138

21332139
@funcCache
21342140
def completeMultipartUpload(self, bucketName, objectKey, uploadId, completeMultipartUploadRequest,
2135-
extensionHeaders=None, encoding_type=None):
2141+
isAttachCrc64=False, extensionHeaders=None, encoding_type=None):
21362142
self._assert_not_null(uploadId, 'uploadId is empty')
21372143
self._assert_not_null(completeMultipartUploadRequest, 'completeMultipartUploadRequest is empty')
21382144
pathArgs = {'uploadId': uploadId}
21392145
if encoding_type is not None:
21402146
pathArgs["encoding-type"] = encoding_type
2147+
entity, headers = self.convertor.trans_complete_multipart_upload_request(completeMultipartUploadRequest, isAttachCrc64)
21412148
ret = self._make_post_request(bucketName, objectKey,
2142-
pathArgs=pathArgs,
2143-
entity=self.convertor.trans_complete_multipart_upload_request(
2144-
completeMultipartUploadRequest), methodName='completeMultipartUpload',
2149+
pathArgs=pathArgs, headers=headers,
2150+
entity=entity, methodName='completeMultipartUpload',
21452151
extensionHeaders=extensionHeaders)
21462152
self._generate_object_url(ret, bucketName, objectKey)
21472153
return ret

src/obs/const.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
DEFAULT_TASK_NUM = 8
9797
DEFAULT_TASK_QUEUE_SIZE = 20000
9898

99-
OBS_SDK_VERSION = '3.24.3'
99+
OBS_SDK_VERSION = '3.24.6'
100100

101101
V2_META_HEADER_PREFIX = 'x-amz-meta-'
102102
V2_HEADER_PREFIX = 'x-amz-'
@@ -223,6 +223,7 @@
223223
ALLOWED_RESPONSE_HTTP_HEADER_METADATA_NAMES = (
224224
'content-type',
225225
'content-md5',
226+
'checksum-crc64ecma',
226227
'content-length',
227228
'content-language',
228229
'expires',

src/obs/convertor.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def security_token_header(self):
110110
def content_sha256_header(self):
111111
return self._get_header_prefix() + 'content-sha256'
112112

113+
def crc64_header(self):
114+
return self._get_header_prefix() + 'checksum-crc64ecma'
115+
113116
def default_storage_class_header(self):
114117
return self._get_header_prefix() + 'storage-class' if self.is_obs else 'x-default-storage-class'
115118

@@ -680,16 +683,21 @@ def _set_configuration(config_type, urn_type):
680683

681684
return ET.tostring(root, 'UTF-8')
682685

683-
@staticmethod
684-
def trans_complete_multipart_upload_request(completeMultipartUploadRequest):
686+
def trans_complete_multipart_upload_request(self, completeMultipartUploadRequest, isAttachCrc64):
685687
root = ET.Element('CompleteMultipartUpload')
688+
headers = {}
689+
686690
parts = [] if completeMultipartUploadRequest.get('parts') is None else (
687691
sorted(completeMultipartUploadRequest['parts'], key=lambda d: d.partNum))
692+
if isAttachCrc64:
693+
object_crc = util.calc_obj_crc_from_parts(parts)
694+
self._put_key_value(headers, self.ha.crc64_header(), object_crc)
695+
688696
for obj in parts:
689697
partEle = ET.SubElement(root, 'Part')
690698
ET.SubElement(partEle, 'PartNumber').text = util.to_string(obj.get('partNum'))
691699
ET.SubElement(partEle, 'ETag').text = util.to_string(obj.get('etag'))
692-
return ET.tostring(root, 'UTF-8')
700+
return (ET.tostring(root, 'UTF-8'), headers)
693701

694702
def trans_restore_object(self, **kwargs):
695703
pathArgs = {'restore': None}
@@ -801,6 +809,8 @@ def trans_restore(self, days, tier):
801809

802810
def trans_put_object(self, **kwargs):
803811
_headers = {}
812+
file_path = kwargs.get('file_path')
813+
content = kwargs.get('content')
804814
metadata = kwargs.get('metadata')
805815
headers = kwargs.get('headers')
806816
if metadata is not None:
@@ -819,7 +829,14 @@ def trans_put_object(self, **kwargs):
819829
self.ha.adapt_storage_class(headers.get('storageClass')))
820830
self._put_key_value(_headers, const.CONTENT_LENGTH_HEADER, headers.get('contentLength'))
821831
self._put_key_value(_headers, self.ha.expires_header(), headers.get('expires'))
822-
832+
if headers.get('crc64') is not None:
833+
self._put_key_value(_headers, self.ha.crc64_header(), headers.get('crc64'))
834+
elif headers.get('isAttachCrc64'):
835+
if file_path:
836+
crc64 = util.calculate_file_crc64(file_path)
837+
else:
838+
crc64 = util.calculate_content_crc64(util.covert_string_to_bytes(content))
839+
self._put_key_value(_headers, self.ha.crc64_header(), crc64)
823840
if self.is_obs:
824841
self._put_key_value(_headers, self.ha.success_action_redirect_header(),
825842
headers.get('successActionRedirect'))
@@ -917,7 +934,7 @@ def trans_copy_object(self, **kwargs):
917934
self._put_key_value(_headers, self.ha.acl_header(), self.ha.adapt_acl_control(headers.get('acl')))
918935
self._put_key_value(_headers, self.ha.storage_class_header(),
919936
self.ha.adapt_storage_class(headers.get('storageClass')))
920-
937+
self._put_key_value(_headers, self.ha.crc64_header(), headers.get('crc64'))
921938
self._put_key_value(_headers, self.ha.metadata_directive_header(), headers.get('directive'))
922939
self._put_key_value(_headers, self.ha.copy_source_if_match_header(), headers.get('if_match'))
923940
self._put_key_value(_headers, self.ha.copy_source_if_none_match_header(), headers.get('if_none_match'))
@@ -1711,6 +1728,7 @@ def parseCompleteMultipartUpload(self, xml, headers=None):
17111728
completeMultipartUploadResponse.sseKms = headers.get(self.ha.sse_kms_header())
17121729
completeMultipartUploadResponse.sseKmsKey = headers.get(self.ha.sse_kms_key_header())
17131730
completeMultipartUploadResponse.sseC = headers.get(self.ha.sse_c_header())
1731+
completeMultipartUploadResponse.crc64 = headers.get(self.ha.crc64_header())
17141732
completeMultipartUploadResponse.sseCKeyMd5 = headers.get(self.ha.sse_c_key_md5_header().lower())
17151733

17161734
return completeMultipartUploadResponse
@@ -1841,6 +1859,7 @@ def parsePutContent(self, headers):
18411859
option.sseC = headers.get(self.ha.sse_c_header())
18421860
option.sseCKeyMd5 = headers.get(self.ha.sse_c_key_md5_header().lower())
18431861
option.etag = headers.get(const.ETAG_HEADER.lower())
1862+
option.crc64 = headers.get(self.ha.crc64_header())
18441863
return option
18451864

18461865
def parseAppendObject(self, headers):
@@ -1852,6 +1871,7 @@ def parseAppendObject(self, headers):
18521871
option.sseCKeyMd5 = headers.get(self.ha.sse_c_key_md5_header().lower())
18531872
option.etag = headers.get(const.ETAG_HEADER.lower())
18541873
option.nextPosition = util.to_long(headers.get(self.ha.next_position_header()))
1874+
option.crc64 = headers.get(self.ha.crc64_header())
18551875
return option
18561876

18571877
def parseInitiateMultipartUpload(self, xml, headers=None):
@@ -1902,6 +1922,8 @@ def _parseGetObjectCommonHeader(self, headers, option):
19021922
option.contentLength = util.to_long(headers.get(const.CONTENT_LENGTH_HEADER.lower()))
19031923
option.contentType = headers.get(const.CONTENT_TYPE_HEADER.lower())
19041924
option.lastModified = headers.get(const.LAST_MODIFIED_HEADER.lower())
1925+
option.crc64 = headers.get(self.ha.crc64_header())
1926+
19051927

19061928
def parseGetObjectMetadata(self, headers):
19071929
option = GetObjectMetadataResponse()
@@ -1940,6 +1962,7 @@ def parseUploadPart(self, headers):
19401962
uploadPartResponse.sseKmsKey = headers.get(self.ha.sse_kms_key_header())
19411963
uploadPartResponse.sseC = headers.get(self.ha.sse_c_header())
19421964
uploadPartResponse.sseCKeyMd5 = headers.get(self.ha.sse_c_key_md5_header().lower())
1965+
uploadPartResponse.crc64 = headers.get(self.ha.crc64_header())
19431966
return uploadPartResponse
19441967

19451968
def parseCopyPart(self, xml, headers=None):
@@ -1952,6 +1975,7 @@ def parseCopyPart(self, xml, headers=None):
19521975
copyPartResponse.sseKmsKey = headers.get(self.ha.sse_kms_key_header())
19531976
copyPartResponse.sseC = headers.get(self.ha.sse_c_header())
19541977
copyPartResponse.sseCKeyMd5 = headers.get(self.ha.sse_c_key_md5_header().lower())
1978+
copyPartResponse.crc64 = headers.get(self.ha.crc64_header())
19551979
return copyPartResponse
19561980

19571981
def parseGetBucketReplication(self, xml, headers=None):

0 commit comments

Comments
 (0)