Skip to content

Commit 0e534ab

Browse files
authored
Merge pull request #267 from l-iberty/master
功能特性更新
2 parents 6e0837a + 100477c commit 0e534ab

File tree

3 files changed

+136
-9
lines changed

3 files changed

+136
-9
lines changed

qcloud_cos/cos_client.py

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def __init__(self, Appid=None, Region=None, SecretId=None, SecretKey=None, Token
4444
Access_id=None, Access_key=None, Secret_id=None, Secret_key=None, Endpoint=None, IP=None, Port=None,
4545
Anonymous=None, UA=None, Proxies=None, Domain=None, ServiceDomain=None, KeepAlive=True, PoolConnections=10,
4646
PoolMaxSize=10, AllowRedirects=False, SignHost=True, EndpointCi=None, EndpointPic=None, EnableOldDomain=True, EnableInternalDomain=True, SignParams=True,
47-
AutoSwitchDomainOnRetry=True):
47+
AutoSwitchDomainOnRetry=False):
4848
"""初始化,保存用户的信息
4949
5050
:param Appid(string): 用户APPID.
@@ -359,6 +359,7 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, ci_req
359359
kwargs['verify'] = False
360360
if self._conf._allow_redirects is not None:
361361
kwargs['allow_redirects'] = self._conf._allow_redirects
362+
exception_logbuf = list() # 记录每次重试的错误日志
362363
for j in range(self._retry + 1):
363364
try:
364365
if j != 0:
@@ -397,13 +398,16 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, ci_req
397398
else:
398399
break
399400
except Exception as e: # 捕获requests抛出的如timeout等客户端错误,转化为客户端错误
400-
logger.exception('url:%s, retry_time:%d exception:%s' % (url, j, str(e)))
401+
# 记录每次请求的exception
402+
exception_log = 'url:%s, retry_time:%d exception:%s' % (url, j, str(e))
403+
exception_logbuf.append(exception_log)
401404
if j < self._retry and (isinstance(e, ConnectionError) or isinstance(e, Timeout)): # 只重试网络错误
402405
if client_can_retry(file_position, **kwargs):
403406
if not domain_switched and self._conf._auto_switch_domain_on_retry and self._conf._ip is None:
404407
url = switch_hostname_for_url(url)
405408
domain_switched = True
406409
continue
410+
logger.exception(exception_logbuf) # 最终重试失败, 输出前几次重试失败的exception
407411
raise CosClientError(str(e))
408412

409413
if not cos_request:
@@ -419,12 +423,16 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, ci_req
419423
if 'x-cos-trace-id' in res.headers:
420424
info['traceid'] = res.headers['x-cos-trace-id']
421425
logger.warn(info)
426+
if len(exception_logbuf) > 0:
427+
logger.exception(exception_logbuf) # 最终重试失败, 输出前几次重试失败的exception
422428
raise CosServiceError(method, info, res.status_code)
423429
else:
424430
msg = res.text
425431
if msg == u'': # 服务器没有返回Error Body时 给出头部的信息
426432
msg = res.headers
427433
logger.error(msg)
434+
if len(exception_logbuf) > 0:
435+
logger.exception(exception_logbuf) # 最终重试失败, 输出前几次重试失败的exception
428436
raise CosServiceError(method, msg, res.status_code)
429437

430438
return None
@@ -1613,7 +1621,7 @@ def head_bucket(self, Bucket, **kwargs):
16131621
16141622
:param Bucket(string): 存储桶名称.
16151623
:param kwargs(dict): 设置请求headers.
1616-
:return: None.
1624+
:return: HEAD Bucket响应头域.
16171625
16181626
.. code-block:: python
16191627
@@ -1635,7 +1643,7 @@ def head_bucket(self, Bucket, **kwargs):
16351643
bucket=Bucket,
16361644
auth=CosS3Auth(self._conf),
16371645
headers=headers)
1638-
return None
1646+
return rt.headers
16391647

16401648
def put_bucket_acl(self, Bucket, AccessControlPolicy={}, **kwargs):
16411649
"""设置bucket ACL
@@ -2926,7 +2934,7 @@ def get_bucket_inventory(self, Bucket, Id, **kwargs):
29262934
return data
29272935

29282936
def delete_bucket_inventory(self, Bucket, Id, **kwargs):
2929-
"""删除bucket 回源配置
2937+
"""删除bucket清单规则
29302938
29312939
:param Bucket(string): 存储桶名称.
29322940
:param Id(string): 清单规则名称.
@@ -2957,6 +2965,52 @@ def delete_bucket_inventory(self, Bucket, Id, **kwargs):
29572965
params=params)
29582966
return None
29592967

2968+
def list_bucket_inventory_configurations(self, Bucket, ContinuationToken=None, **kwargs):
2969+
"""列举存储桶清单规则
2970+
2971+
:param Bucket(string): 存储桶名称
2972+
:param ContinuationToken(string): 分页参数, 用以获取下一页信息
2973+
:param kwargs(dict): 设置请求headers.
2974+
:return(dict): 存储桶清单规则列表
2975+
2976+
.. code-block:: python
2977+
2978+
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
2979+
client = CosS3Client(config)
2980+
# 分页列举bucket清单规则
2981+
continuation_token = ''
2982+
while True:
2983+
resp = client.list_bucket_inventory_configurations(
2984+
Bucket=bucket,
2985+
ContinuationToken=continuation_token,
2986+
)
2987+
if 'InventoryConfiguration' in resp:
2988+
for conf in resp['InventoryConfiguration']:
2989+
print(conf)
2990+
if resp['IsTruncated'] == 'true':
2991+
continuation_token = resp['NextContinuationToken']
2992+
else:
2993+
break
2994+
"""
2995+
headers = mapped(kwargs)
2996+
params = {'inventory': ''}
2997+
if ContinuationToken is not None:
2998+
params['continuation-token'] = ContinuationToken
2999+
url = self._conf.uri(bucket=Bucket)
3000+
logger.info("list bucket inventory configurations, url={url}, headers={headers}".format(
3001+
url=url,
3002+
headers=headers,
3003+
))
3004+
rt = self.send_request(
3005+
method='GET',
3006+
url=url,
3007+
bucket=Bucket,
3008+
auth=CosS3Auth(self._conf, params=params),
3009+
headers=headers,
3010+
params=params)
3011+
data = xml_to_dict(rt.content)
3012+
return data
3013+
29603014
def put_object_tagging(self, Bucket, Key, Tagging={}, **kwargs):
29613015
"""设置object的标签
29623016
@@ -3560,7 +3614,7 @@ def _check_all_upload_parts(self, bucket, key, uploadid, local_path, parts_num,
35603614
already_exist_parts[part_num] = part['ETag']
35613615
return True
35623616

3563-
def download_file(self, Bucket, Key, DestFilePath, PartSize=20, MAXThread=5, EnableCRC=False, progress_callback=None, **Kwargs):
3617+
def download_file(self, Bucket, Key, DestFilePath, PartSize=20, MAXThread=5, EnableCRC=False, progress_callback=None, DumpRecordDir=None, **Kwargs):
35643618
"""小于等于20MB的文件简单下载,大于20MB的文件使用续传下载
35653619
35663620
:param Bucket(string): 存储桶名称.
@@ -3596,7 +3650,7 @@ def download_file(self, Bucket, Key, DestFilePath, PartSize=20, MAXThread=5, Ena
35963650
callback = ProgressCallback(file_size, progress_callback)
35973651

35983652
downloader = ResumableDownLoader(self, Bucket, Key, DestFilePath, object_info, PartSize, MAXThread, EnableCRC,
3599-
callback, **Kwargs)
3653+
callback, DumpRecordDir, **Kwargs)
36003654
downloader.start()
36013655

36023656
def upload_file(self, Bucket, Key, LocalFilePath, PartSize=1, MAXThread=5, EnableMD5=False, progress_callback=None,

qcloud_cos/resumable_downloader.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
class ResumableDownLoader(object):
1919
def __init__(self, cos_client, bucket, key, dest_filename, object_info, part_size=20, max_thread=5,
20-
enable_crc=False, progress_callback=None, **kwargs):
20+
enable_crc=False, progress_callback=None, dump_record_dir = None, **kwargs):
2121
self.__cos_client = cos_client
2222
self.__bucket = bucket
2323
self.__key = key
@@ -34,7 +34,10 @@ def __init__(self, cos_client, bucket, key, dest_filename, object_info, part_siz
3434
self.__finished_parts = []
3535
self.__lock = threading.Lock()
3636
self.__record = None # 记录当前的上下文
37-
self.__dump_record_dir = os.path.join(os.path.expanduser('~'), '.cos_download_tmp_file')
37+
if not dump_record_dir:
38+
self.__dump_record_dir = os.path.join(os.path.expanduser('~'), '.cos_download_tmp_file')
39+
else:
40+
self.__dump_record_dir = dump_record_dir
3841

3942
record_filename = self.__get_record_filename(bucket, key, self.__dest_file_path)
4043
self.__record_filepath = os.path.join(self.__dump_record_dir, record_filename)

ut/test.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1506,6 +1506,76 @@ def test_put_get_delete_bucket_inventory():
15061506
Id='test'
15071507
)
15081508

1509+
def test_list_bucket_inventory_configrations():
1510+
"""测试列举bucket清单"""
1511+
inventory_config = {
1512+
'Destination': {
1513+
'COSBucketDestination': {
1514+
'AccountId': '2832742109',
1515+
'Bucket': 'qcs::cos:' + REGION + '::' + test_bucket,
1516+
'Format': 'CSV',
1517+
'Prefix': 'list1',
1518+
'Encryption': {
1519+
'SSECOS': {}
1520+
}
1521+
}
1522+
},
1523+
'IsEnabled': 'True',
1524+
'Filter': {
1525+
'Prefix': 'filterPrefix'
1526+
},
1527+
'IncludedObjectVersions': 'All',
1528+
'OptionalFields': {
1529+
'Field': [
1530+
'Size',
1531+
'LastModifiedDate',
1532+
'ETag',
1533+
'StorageClass',
1534+
'IsMultipartUploaded',
1535+
'ReplicationStatus'
1536+
]
1537+
},
1538+
'Schedule': {
1539+
'Frequency': 'Daily'
1540+
}
1541+
}
1542+
# 构建150条清单配置规则(清单分页大小为100条规则)
1543+
n = 150
1544+
for i in range(n):
1545+
id = 'ID-{}'.format(i)
1546+
response = client.put_bucket_inventory(
1547+
Bucket=test_bucket,
1548+
Id=id,
1549+
InventoryConfiguration=inventory_config,
1550+
)
1551+
1552+
# 列举清单
1553+
i = 0
1554+
continuation_token = ''
1555+
while True:
1556+
resp = client.list_bucket_inventory_configurations(
1557+
Bucket=test_bucket,
1558+
ContinuationToken=continuation_token,
1559+
)
1560+
if 'InventoryConfiguration' in resp:
1561+
for conf in resp['InventoryConfiguration']:
1562+
id = 'ID-{}'.format(i)
1563+
assert id == conf['Id']
1564+
i += 1
1565+
if resp['IsTruncated'] == 'true':
1566+
continuation_token = resp['NextContinuationToken']
1567+
else:
1568+
break
1569+
1570+
assert i == n
1571+
1572+
# 删除清单
1573+
for i in range(n):
1574+
id = 'ID-{}'.format(i)
1575+
response = client.delete_bucket_inventory(
1576+
Bucket=test_bucket,
1577+
Id=id,
1578+
)
15091579

15101580
def test_put_get_delete_bucket_tagging():
15111581
"""测试设置获取删除bucket标签"""

0 commit comments

Comments
 (0)