1111import json
1212import xml .dom .minidom
1313import xml .etree .ElementTree
14- from requests import Request , Session
14+ from requests import Request , Session , ConnectionError , Timeout
1515from datetime import datetime
1616from six .moves .urllib .parse import quote , unquote , urlencode
17+ from six import text_type , binary_type
1718from hashlib import md5
1819from dicttoxml import dicttoxml
1920from .streambody import StreamBody
2627from .cos_exception import CosServiceError
2728from .version import __version__
2829from .select_event_stream import EventStream
30+ from .resumable_downloader import ResumableDownLoader
2931logger = logging .getLogger (__name__ )
3032
3133
@@ -186,7 +188,7 @@ def __init__(self, conf, retry=1, session=None):
186188 else :
187189 self ._session = session
188190
189- def get_conf ():
191+ def get_conf (self ):
190192 """获取配置"""
191193 return self ._conf
192194
@@ -237,7 +239,12 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, **kwar
237239 elif bucket is not None :
238240 kwargs ['headers' ]['Host' ] = self ._conf .get_host (bucket )
239241 kwargs ['headers' ] = format_values (kwargs ['headers' ])
242+
243+ file_position = None
240244 if 'data' in kwargs :
245+ body = kwargs ['data' ]
246+ if hasattr (body , 'tell' ) and hasattr (body , 'seek' ) and hasattr (body , 'read' ):
247+ file_position = body .tell () # 记录文件当前位置
241248 kwargs ['data' ] = to_bytes (kwargs ['data' ])
242249 if self ._conf ._ip is not None and self ._conf ._scheme == 'https' :
243250 kwargs ['verify' ] = False
@@ -259,10 +266,16 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, **kwar
259266 return res
260267 elif res .status_code < 500 : # 4xx 不重试
261268 break
269+ else :
270+ if j < self ._retry and client_can_retry (file_position , ** kwargs ):
271+ continue
272+ else :
273+ break
262274 except Exception as e : # 捕获requests抛出的如timeout等客户端错误,转化为客户端错误
263275 logger .exception ('url:%s, retry_time:%d exception:%s' % (url , j , str (e )))
264- if j < self ._retry :
265- continue
276+ if j < self ._retry and (isinstance (e , ConnectionError ) or isinstance (e , Timeout )): # 只重试网络错误
277+ if client_can_retry (file_position , ** kwargs ):
278+ continue
266279 raise CosClientError (str (e ))
267280
268281 if not cos_request :
@@ -277,7 +290,7 @@ def send_request(self, method, url, bucket, timeout=30, cos_request=True, **kwar
277290 info ['requestid' ] = res .headers ['x-cos-request-id' ]
278291 if 'x-cos-trace-id' in res .headers :
279292 info ['traceid' ] = res .headers ['x-cos-trace-id' ]
280- logger .error (info )
293+ logger .warn (info )
281294 raise CosServiceError (method , info , res .status_code )
282295 else :
283296 msg = res .text
@@ -1028,6 +1041,7 @@ def get_object_acl(self, Bucket, Key, **kwargs):
10281041 lst = []
10291042 lst .append (data ['AccessControlList' ]['Grant' ])
10301043 data ['AccessControlList' ]['Grant' ] = lst
1044+ data ['CannedACL' ] = parse_object_canned_acl (data , rt .headers )
10311045 return data
10321046
10331047 def restore_object (self , Bucket , Key , RestoreRequest = {}, ** kwargs ):
@@ -1478,6 +1492,7 @@ def get_bucket_acl(self, Bucket, **kwargs):
14781492 lst = []
14791493 lst .append (data ['AccessControlList' ]['Grant' ])
14801494 data ['AccessControlList' ]['Grant' ] = lst
1495+ data ['CannedACL' ] = parse_bucket_canned_acl (data )
14811496 return data
14821497
14831498 def put_bucket_cors (self , Bucket , CORSConfiguration = {}, ** kwargs ):
@@ -2798,6 +2813,38 @@ def get_bucket_referer(self, Bucket, **kwargs):
27982813 format_dict (data ['DomainList' ], ['Domain' ])
27992814 return data
28002815
2816+ def delete_bucket_referer (self , Bucket , ** kwargs ):
2817+ """删除bucket防盗链规则
2818+
2819+ :param Bucket(string): 存储桶名称.
2820+ :param kwargs(dict): 设置请求headers.
2821+ :return(dict): None.
2822+
2823+ .. code-block:: python
2824+
2825+ config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
2826+ client = CosS3Client(config)
2827+ # 获取bucket标签
2828+ response = client.delete_bucket_referer(
2829+ Bucket='bucket'
2830+ )
2831+ """
2832+ xml_config = ''
2833+ headers = mapped (kwargs )
2834+ headers ['Content-MD5' ] = get_md5 (xml_config )
2835+ headers ['Content-Type' ] = 'application/xml'
2836+ params = {'referer' : '' }
2837+ url = self ._conf .uri (bucket = Bucket )
2838+ rt = self .send_request (
2839+ method = 'PUT' ,
2840+ url = url ,
2841+ bucket = Bucket ,
2842+ data = xml_config ,
2843+ auth = CosS3Auth (self ._conf , params = params ),
2844+ headers = headers ,
2845+ params = params )
2846+ return None
2847+
28012848 # service interface begin
28022849 def list_buckets (self , ** kwargs ):
28032850 """列出所有bucket
@@ -2809,9 +2856,7 @@ def list_buckets(self, **kwargs):
28092856 config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
28102857 client = CosS3Client(config)
28112858 # 获取账户下所有存储桶信息
2812- response = logging_client.list_buckets(
2813- Bucket='bucket'
2814- )
2859+ response = client.list_buckets()
28152860 """
28162861 headers = mapped (kwargs )
28172862 url = '{scheme}://service.cos.myqcloud.com/' .format (scheme = self ._conf ._scheme )
@@ -2945,6 +2990,30 @@ def _check_all_upload_parts(self, bucket, key, uploadid, local_path, parts_num,
29452990 already_exist_parts [part_num ] = part ['ETag' ]
29462991 return True
29472992
2993+ def download_file (self , Bucket , Key , DestFilePath , PartSize = 20 , MAXThread = 5 , EnableCRC = False , ** Kwargs ):
2994+ """小于等于20MB的文件简单下载,大于20MB的文件使用续传下载
2995+
2996+ :param Bucket(string): 存储桶名称.
2997+ :param key(string): COS文件的路径名.
2998+ :param DestFilePath(string): 下载文件的目的路径.
2999+ :param PartSize(int): 分块下载的大小设置,单位为MB.
3000+ :param MAXThread(int): 并发下载的最大线程数.
3001+ :param EnableCRC(bool): 校验下载文件与源文件是否一致
3002+ :param kwargs(dict): 设置请求headers.
3003+ """
3004+ logger .debug ("Start to download file, bucket: {0}, key: {1}, dest_filename: {2}, part_size: {3}MB,\
3005+ max_thread: {4}" .format (Bucket , Key , DestFilePath , PartSize , MAXThread ))
3006+
3007+ object_info = self .head_object (Bucket , Key )
3008+ file_size = int (object_info ['Content-Length' ])
3009+ if file_size <= 1024 * 1024 * 20 :
3010+ response = self .get_object (Bucket , Key , ** Kwargs )
3011+ response ['Body' ].get_stream_to_file (DestFilePath )
3012+ return
3013+
3014+ downloader = ResumableDownLoader (self , Bucket , Key , DestFilePath , object_info , PartSize , MAXThread , EnableCRC , ** Kwargs )
3015+ downloader .start ()
3016+
29483017 def upload_file (self , Bucket , Key , LocalFilePath , PartSize = 1 , MAXThread = 5 , EnableMD5 = False , ** kwargs ):
29493018 """小于等于20MB的文件简单上传,大于20MB的文件使用分块上传
29503019
@@ -3039,7 +3108,7 @@ def _inner_head_object(self, CopySource):
30393108 params = {}
30403109 if versionid != '' :
30413110 params ['versionId' ] = versionid
3042- url = u"{scheme}://{bucket}.{endpoint}/{path}" .format (scheme = self ._conf ._scheme , bucket = bucket , endpoint = endpoint , path = path )
3111+ url = u"{scheme}://{bucket}.{endpoint}/{path}" .format (scheme = self ._conf ._scheme , bucket = bucket , endpoint = endpoint , path = quote ( to_bytes ( path ), '/-_.~' ) )
30433112 rt = self .send_request (
30443113 method = 'HEAD' ,
30453114 url = url ,
@@ -3403,6 +3472,85 @@ def update_object_meta(self, Bucket, Key, **kwargs):
34033472 )
34043473 return response
34053474
3475+ def put_bucket_encryption (self , Bucket , ServerSideEncryptionConfiguration = {}, ** kwargs ):
3476+ """设置执行存储桶下的默认加密配置
3477+
3478+ :param Bucket(string): 存储桶名称.
3479+ :param ServerSideEncryptionConfiguration(dict): 设置Bucket的加密规则
3480+ :param kwargs(dict): 设置请求的headers.
3481+ :return: None.
3482+ """
3483+ # 类型为list的标签
3484+ lst = [
3485+ '<Rule>' ,
3486+ '</Rule>'
3487+ ]
3488+ xml_config = format_xml (data = ServerSideEncryptionConfiguration , root = 'ServerSideEncryptionConfiguration' , lst = lst )
3489+ headers = mapped (kwargs )
3490+ params = {'encryption' : '' }
3491+ url = self ._conf .uri (bucket = Bucket )
3492+ logger .info ("put bucket encryption, url=:{url} ,headers=:{headers}" .format (
3493+ url = url ,
3494+ headers = headers ))
3495+ rt = self .send_request (
3496+ method = 'PUT' ,
3497+ url = url ,
3498+ bucket = Bucket ,
3499+ auth = CosS3Auth (self ._conf , params = params ),
3500+ data = xml_config ,
3501+ headers = headers ,
3502+ params = params )
3503+
3504+ return None
3505+
3506+ def get_bucket_encryption (self , Bucket , ** kwargs ):
3507+ """获取存储桶下的默认加密配置
3508+
3509+ :param Bucket(string): 存储桶名称.
3510+ :param kwargs(dict): 设置请求的headers.
3511+ :return(dict): 返回bucket的加密规则.
3512+ """
3513+ headers = mapped (kwargs )
3514+ params = {'encryption' : '' }
3515+ url = self ._conf .uri (bucket = Bucket )
3516+ logger .info ("get bucket encryption, url=:{url} ,headers=:{headers}" .format (
3517+ url = url ,
3518+ headers = headers ))
3519+ rt = self .send_request (
3520+ method = 'GET' ,
3521+ url = url ,
3522+ bucket = Bucket ,
3523+ auth = CosS3Auth (self ._conf , params = params ),
3524+ headers = headers ,
3525+ params = params )
3526+
3527+ data = xml_to_dict (rt .content )
3528+ format_dict (data , ['Rule' ])
3529+ return data
3530+
3531+ def delete_bucket_encryption (self , Bucket , ** kwargs ):
3532+ """用于删除指定存储桶下的默认加密配置
3533+
3534+ :param Bucket(string): 存储桶名称.
3535+ :param kwargs(dict): 设置请求的headers.
3536+ :return: None.
3537+ """
3538+ headers = mapped (kwargs )
3539+ params = {'encryption' : '' }
3540+ url = self ._conf .uri (bucket = Bucket )
3541+ logger .info ("delete bucket encryption, url=:{url} ,headers=:{headers}" .format (
3542+ url = url ,
3543+ headers = headers ))
3544+ rt = self .send_request (
3545+ method = 'DELETE' ,
3546+ url = url ,
3547+ bucket = Bucket ,
3548+ auth = CosS3Auth (self ._conf , params = params ),
3549+ headers = headers ,
3550+ params = params )
3551+
3552+ return None
3553+
34063554 def put_async_fetch_task (self , Bucket , FetchTaskConfiguration = {}, ** kwargs ):
34073555 """发起异步拉取对象到COS的任务
34083556
0 commit comments