第一次提交:阿龙电竞 Django 后端最新版本
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
from .cos_client import CosS3Client
|
||||
from .cos_client import CosConfig
|
||||
from .cos_exception import CosServiceError
|
||||
from .cos_exception import CosClientError
|
||||
from .cos_auth import CosS3Auth
|
||||
from .cos_comm import get_date
|
||||
from .meta_insight import MetaInsightClient
|
||||
from .ai_recognition import AIRecognitionClient
|
||||
|
||||
import logging
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
logging.getLogger(__name__).addHandler(NullHandler())
|
||||
1048
venv_prod/lib/python3.12/site-packages/qcloud_cos/ai_recognition.py
Normal file
1048
venv_prod/lib/python3.12/site-packages/qcloud_cos/ai_recognition.py
Normal file
File diff suppressed because it is too large
Load Diff
198
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_auth.py
Normal file
198
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_auth.py
Normal file
@@ -0,0 +1,198 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from six.moves.urllib.parse import quote, unquote, urlparse, urlencode
|
||||
import hmac
|
||||
import time
|
||||
import hashlib
|
||||
import logging
|
||||
from requests.auth import AuthBase
|
||||
from .cos_comm import to_unicode, to_bytes, to_str
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def filter_headers(data):
|
||||
"""只设置host content-type 还有x开头的头部.
|
||||
|
||||
:param data(dict): 所有的头部信息.
|
||||
:return(dict): 计算进签名的头部.
|
||||
"""
|
||||
valid_headers = [
|
||||
"cache-control",
|
||||
"content-disposition",
|
||||
"content-encoding",
|
||||
"content-type",
|
||||
"content-md5",
|
||||
"content-length",
|
||||
"expect",
|
||||
"expires",
|
||||
"host",
|
||||
"if-match",
|
||||
"if-modified-since",
|
||||
"if-none-match",
|
||||
"if-unmodified-since",
|
||||
"origin",
|
||||
"range",
|
||||
"transfer-encoding",
|
||||
"pic-operations",
|
||||
]
|
||||
headers = {}
|
||||
for i in data:
|
||||
if str.lower(i) in valid_headers or str.lower(i).startswith("x-cos-") or str.lower(i).startswith("x-ci-"):
|
||||
headers[i] = data[i]
|
||||
return headers
|
||||
|
||||
|
||||
class CosS3Auth(AuthBase):
|
||||
|
||||
def __init__(self, conf, key=None, params={}, expire=10000, sign_host=None):
|
||||
self._secret_id = conf._secret_id if conf._secret_id else \
|
||||
(conf._credential_inst.secret_id if conf._credential_inst else None)
|
||||
self._secret_key = conf._secret_key if conf._secret_key else \
|
||||
(conf._credential_inst.secret_key if conf._credential_inst else None)
|
||||
self._anonymous = conf._anonymous
|
||||
self._expire = expire
|
||||
self._params = params
|
||||
self._sign_params = conf._sign_params
|
||||
|
||||
# 如果API指定了是否签名host,则以具体API为准,如果未指定则以配置为准
|
||||
if sign_host is not None:
|
||||
self._sign_host = bool(sign_host)
|
||||
else:
|
||||
self._sign_host = conf._sign_host
|
||||
|
||||
if key:
|
||||
key = to_unicode(key)
|
||||
if key[0] == u'/':
|
||||
self._path = key
|
||||
else:
|
||||
self._path = u'/' + key
|
||||
else:
|
||||
self._path = u'/'
|
||||
|
||||
def __call__(self, r):
|
||||
|
||||
# 匿名请求直接返回
|
||||
if self._anonymous:
|
||||
r.headers['Authorization'] = ""
|
||||
logger.debug("anonymous reqeust")
|
||||
return r
|
||||
|
||||
path = self._path
|
||||
uri_params = {}
|
||||
if self._sign_params:
|
||||
uri_params = self._params
|
||||
headers = filter_headers(r.headers)
|
||||
|
||||
# 如果headers中不包含host头域,则从url中提取host,并且加入签名计算
|
||||
if self._sign_host:
|
||||
|
||||
# 判断headers中是否包含host头域
|
||||
contain_host = False
|
||||
for i in headers:
|
||||
if str.lower(i) == "host": # 兼容host/Host/HOST等
|
||||
contain_host = True
|
||||
break
|
||||
|
||||
# 从url中提取host
|
||||
if not contain_host:
|
||||
url_parsed = urlparse(r.url)
|
||||
if url_parsed.hostname is not None:
|
||||
headers["host"] = url_parsed.hostname
|
||||
|
||||
# reserved keywords in headers urlencode are -_.~, notice that / should be encoded and space should not be encoded to plus sign(+)
|
||||
headers = dict([(quote(to_bytes(to_str(k)), '-_.~').lower(), quote(to_bytes(to_str(v)), '-_.~')) for k, v in
|
||||
headers.items()]) # headers中的key转换为小写,value进行encode
|
||||
uri_params = dict([(quote(to_bytes(to_str(k)), '-_.~').lower(), quote(to_bytes(to_str(v)), '-_.~')) for k, v in
|
||||
uri_params.items()])
|
||||
format_str = u"{method}\n{host}\n{params}\n{headers}\n".format(
|
||||
method=r.method.lower(),
|
||||
host=path,
|
||||
params='&'.join(map(lambda tupl: "%s=%s" %
|
||||
(tupl[0], tupl[1]), sorted(uri_params.items()))),
|
||||
headers='&'.join(map(lambda tupl: "%s=%s" %
|
||||
(tupl[0], tupl[1]), sorted(headers.items())))
|
||||
)
|
||||
logger.debug("format str: " + format_str)
|
||||
|
||||
start_sign_time = int(time.time())
|
||||
sign_time = "{bg_time};{ed_time}".format(
|
||||
bg_time=start_sign_time - 60, ed_time=start_sign_time + self._expire)
|
||||
sha1 = hashlib.sha1()
|
||||
sha1.update(to_bytes(format_str))
|
||||
|
||||
str_to_sign = "sha1\n{time}\n{sha1}\n".format(
|
||||
time=sign_time, sha1=sha1.hexdigest())
|
||||
logger.debug('str_to_sign: ' + str(str_to_sign))
|
||||
sign_key = hmac.new(to_bytes(self._secret_key), to_bytes(
|
||||
sign_time), hashlib.sha1).hexdigest()
|
||||
sign = hmac.new(to_bytes(sign_key), to_bytes(
|
||||
str_to_sign), hashlib.sha1).hexdigest()
|
||||
logger.debug('sign_key: ' + str(sign_key))
|
||||
logger.debug('sign: ' + str(sign))
|
||||
sign_tpl = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-header-list={headers}&q-url-param-list={params}&q-signature={sign}"
|
||||
|
||||
r.headers['Authorization'] = sign_tpl.format(
|
||||
ak=self._secret_id,
|
||||
sign_time=sign_time,
|
||||
key_time=sign_time,
|
||||
params=';'.join(sorted(uri_params.keys())),
|
||||
headers=';'.join(sorted(headers.keys())),
|
||||
sign=sign
|
||||
)
|
||||
logger.debug("sign_key" + str(sign_key))
|
||||
logger.debug(r.headers['Authorization'])
|
||||
logger.debug("request headers: " + str(r.headers))
|
||||
return r
|
||||
|
||||
|
||||
class CosRtmpAuth(AuthBase):
|
||||
|
||||
def __init__(self, conf, bucket=None, channel=None, params={}, expire=3600, presign_expire=0):
|
||||
self._secret_id = conf._secret_id
|
||||
self._secret_key = conf._secret_key
|
||||
self._token = conf._token
|
||||
self._anonymous = conf._anonymous
|
||||
self._expire = expire
|
||||
self._presign_expire = presign_expire
|
||||
self._params = params
|
||||
if self._token:
|
||||
self._params['q-token'] = self._token
|
||||
self._path = u'/' + bucket + u'/' + channel
|
||||
|
||||
def get_rtmp_sign(self):
|
||||
# get rtmp string
|
||||
canonicalized_param = ''
|
||||
for k, v in self._params.items():
|
||||
canonicalized_param += '{key}={value}&'.format(key=k, value=v)
|
||||
if self._presign_expire >= 60:
|
||||
canonicalized_param += 'presign={value}'.format(
|
||||
value=self._presign_expire)
|
||||
canonicalized_param = canonicalized_param.rstrip('&')
|
||||
rtmp_str = u"{path}\n{params}\n".format(
|
||||
path=self._path, params=canonicalized_param)
|
||||
logger.debug("rtmp str: " + rtmp_str)
|
||||
|
||||
sha1 = hashlib.sha1()
|
||||
sha1.update(to_bytes(rtmp_str))
|
||||
# get time
|
||||
sign_time = int(time.time())
|
||||
sign_time_str = "{start_time};{end_time}".format(
|
||||
start_time=sign_time - 60, end_time=sign_time + self._expire)
|
||||
str_to_sign = "sha1\n{time}\n{sha1}\n".format(
|
||||
time=sign_time_str, sha1=sha1.hexdigest())
|
||||
logger.debug('str_to_sign: ' + str(str_to_sign))
|
||||
# get sinature
|
||||
signature = hmac.new(to_bytes(self._secret_key), to_bytes(
|
||||
str_to_sign), hashlib.sha1).hexdigest()
|
||||
logger.debug('signature: ' + str(signature))
|
||||
rtmp_sign = "q-sign-algorithm=sha1&q-ak={ak}&q-sign-time={sign_time}&q-key-time={key_time}&q-signature={sign}".format(
|
||||
ak=self._secret_id, sign_time=sign_time_str, key_time=sign_time_str, sign=signature)
|
||||
if canonicalized_param != '':
|
||||
return rtmp_sign + "&{params}".format(params=canonicalized_param)
|
||||
else:
|
||||
return rtmp_sign
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
11262
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_client.py
Normal file
11262
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_client.py
Normal file
File diff suppressed because it is too large
Load Diff
596
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_comm.py
Normal file
596
venv_prod/lib/python3.12/site-packages/qcloud_cos/cos_comm.py
Normal file
@@ -0,0 +1,596 @@
|
||||
# -*- coding=utf-8
|
||||
|
||||
from six import text_type, binary_type, string_types
|
||||
from six.moves.urllib.parse import quote, unquote, urlparse
|
||||
import hashlib
|
||||
import base64
|
||||
import os
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
import threading
|
||||
import xml.dom.minidom
|
||||
import xml.etree.ElementTree
|
||||
from datetime import datetime
|
||||
from xmltodict import unparse
|
||||
from .xml2dict import Xml2Dict
|
||||
from .cos_exception import CosClientError
|
||||
from .cos_exception import CosServiceError
|
||||
|
||||
SINGLE_UPLOAD_LENGTH = 5 * 1024 * 1024 * 1024 # 单次上传文件最大为5GB
|
||||
DEFAULT_CHUNK_SIZE = 1024 * 1024 # 计算MD5值时,文件单次读取的块大小为1MB
|
||||
# kwargs中params到http headers的映射
|
||||
maplist = {
|
||||
'ContentLength': 'Content-Length',
|
||||
'ContentMD5': 'Content-MD5',
|
||||
'ContentType': 'Content-Type',
|
||||
'CacheControl': 'Cache-Control',
|
||||
'ContentDisposition': 'Content-Disposition',
|
||||
'ContentEncoding': 'Content-Encoding',
|
||||
'ContentLanguage': 'Content-Language',
|
||||
'Expires': 'Expires',
|
||||
'ResponseContentType': 'response-content-type',
|
||||
'ResponseContentLanguage': 'response-content-language',
|
||||
'ResponseExpires': 'response-expires',
|
||||
'ResponseCacheControl': 'response-cache-control',
|
||||
'ResponseContentDisposition': 'response-content-disposition',
|
||||
'ResponseContentEncoding': 'response-content-encoding',
|
||||
'Metadata': 'Metadata',
|
||||
'ACL': 'x-cos-acl',
|
||||
'Tagging': 'x-cos-tagging',
|
||||
'GrantFullControl': 'x-cos-grant-full-control',
|
||||
'GrantWrite': 'x-cos-grant-write',
|
||||
'GrantRead': 'x-cos-grant-read',
|
||||
'StorageClass': 'x-cos-storage-class',
|
||||
'Range': 'Range',
|
||||
'IfMatch': 'If-Match',
|
||||
'IfNoneMatch': 'If-None-Match',
|
||||
'IfModifiedSince': 'If-Modified-Since',
|
||||
'IfUnmodifiedSince': 'If-Unmodified-Since',
|
||||
'CopySourceIfMatch': 'x-cos-copy-source-If-Match',
|
||||
'CopySourceIfNoneMatch': 'x-cos-copy-source-If-None-Match',
|
||||
'CopySourceIfModifiedSince': 'x-cos-copy-source-If-Modified-Since',
|
||||
'CopySourceIfUnmodifiedSince': 'x-cos-copy-source-If-Unmodified-Since',
|
||||
'VersionId': 'versionId',
|
||||
'ServerSideEncryption': 'x-cos-server-side-encryption',
|
||||
'SSEKMSKeyId': 'x-cos-server-side-encryption-cos-kms-key-id',
|
||||
'SSEKMSContext': 'x-cos-server-side-encryption-context',
|
||||
'SSECustomerAlgorithm': 'x-cos-server-side-encryption-customer-algorithm',
|
||||
'SSECustomerKey': 'x-cos-server-side-encryption-customer-key',
|
||||
'SSECustomerKeyMD5': 'x-cos-server-side-encryption-customer-key-MD5',
|
||||
'CopySourceSSECustomerAlgorithm': 'x-cos-copy-source-server-side-encryption-customer-algorithm',
|
||||
'CopySourceSSECustomerKey': 'x-cos-copy-source-server-side-encryption-customer-key',
|
||||
'CopySourceSSECustomerKeyMD5': 'x-cos-copy-source-server-side-encryption-customer-key-MD5',
|
||||
'Referer': 'Referer',
|
||||
'PicOperations': 'Pic-Operations',
|
||||
'TrafficLimit': 'x-cos-traffic-limit',
|
||||
'Accept': 'Accept',
|
||||
'AcceptEncoding': 'Accept-Encoding',
|
||||
}
|
||||
|
||||
|
||||
def to_str(s):
|
||||
"""非字符串转换为字符串"""
|
||||
if isinstance(s, text_type) or isinstance(s, binary_type):
|
||||
return s
|
||||
return str(s)
|
||||
|
||||
|
||||
def to_unicode(s):
|
||||
"""将字符串转为unicode"""
|
||||
if isinstance(s, binary_type):
|
||||
try:
|
||||
return s.decode('utf-8')
|
||||
except UnicodeDecodeError as e:
|
||||
raise CosClientError('your bytes strings can not be decoded in utf8, utf8 support only!')
|
||||
return s
|
||||
|
||||
|
||||
def to_bytes(s):
|
||||
"""将字符串转为bytes"""
|
||||
if isinstance(s, text_type):
|
||||
try:
|
||||
return s.encode('utf-8')
|
||||
except UnicodeEncodeError as e:
|
||||
raise CosClientError('your unicode strings can not encoded in utf8, utf8 support only!')
|
||||
return s
|
||||
|
||||
|
||||
def get_raw_md5(data):
|
||||
"""计算md5 md5的输入必须为bytes"""
|
||||
data = to_bytes(data)
|
||||
m2 = hashlib.md5(data)
|
||||
etag = '"' + str(m2.hexdigest()) + '"'
|
||||
return etag
|
||||
|
||||
|
||||
def get_md5(data):
|
||||
"""计算 base64 md5 md5的输入必须为bytes"""
|
||||
data = to_bytes(data)
|
||||
m2 = hashlib.md5(data)
|
||||
MD5 = base64.standard_b64encode(m2.digest())
|
||||
return MD5
|
||||
|
||||
|
||||
def get_content_md5(body):
|
||||
"""计算任何输入流的md5值"""
|
||||
if isinstance(body, text_type) or isinstance(body, binary_type):
|
||||
return get_md5(body)
|
||||
elif hasattr(body, 'tell') and hasattr(body, 'seek') and hasattr(body, 'read'):
|
||||
file_position = body.tell() # 记录文件当前位置
|
||||
# avoid OOM
|
||||
md5 = hashlib.md5()
|
||||
chunk = body.read(DEFAULT_CHUNK_SIZE)
|
||||
while chunk:
|
||||
md5.update(to_bytes(chunk))
|
||||
chunk = body.read(DEFAULT_CHUNK_SIZE)
|
||||
md5_str = base64.standard_b64encode(md5.digest())
|
||||
try:
|
||||
body.seek(file_position) # 恢复初始的文件位置
|
||||
except Exception as e:
|
||||
raise CosClientError('seek unsupported to calculate md5!')
|
||||
return md5_str
|
||||
else:
|
||||
raise CosClientError('unsupported body type to calculate md5!')
|
||||
return None
|
||||
|
||||
|
||||
def dict_to_xml(data):
|
||||
"""V5使用xml格式,将输入的dict转换为xml"""
|
||||
doc = xml.dom.minidom.Document()
|
||||
root = doc.createElement('CompleteMultipartUpload')
|
||||
doc.appendChild(root)
|
||||
|
||||
if 'Part' not in data:
|
||||
raise CosClientError("Invalid Parameter, Part Is Required!")
|
||||
|
||||
for i in data['Part']:
|
||||
nodePart = doc.createElement('Part')
|
||||
|
||||
if 'PartNumber' not in i:
|
||||
raise CosClientError("Invalid Parameter, PartNumber Is Required!")
|
||||
|
||||
nodeNumber = doc.createElement('PartNumber')
|
||||
nodeNumber.appendChild(doc.createTextNode(str(i['PartNumber'])))
|
||||
|
||||
if 'ETag' not in i:
|
||||
raise CosClientError("Invalid Parameter, ETag Is Required!")
|
||||
|
||||
nodeETag = doc.createElement('ETag')
|
||||
nodeETag.appendChild(doc.createTextNode(str(i['ETag'])))
|
||||
|
||||
nodePart.appendChild(nodeNumber)
|
||||
nodePart.appendChild(nodeETag)
|
||||
root.appendChild(nodePart)
|
||||
return doc.toxml('utf-8')
|
||||
|
||||
|
||||
def xml_to_dict(data, origin_str="", replace_str=""):
|
||||
"""V5使用xml格式,将response中的xml转换为dict"""
|
||||
root = xml.etree.ElementTree.fromstring(data)
|
||||
xmldict = Xml2Dict(root)
|
||||
xmlstr = str(xmldict)
|
||||
xmlstr = xmlstr.replace("{http://www.qcloud.com/document/product/436/7751}", "")
|
||||
xmlstr = xmlstr.replace("{https://cloud.tencent.com/document/product/436}", "")
|
||||
xmlstr = xmlstr.replace("{http://doc.s3.amazonaws.com/2006-03-01}", "")
|
||||
xmlstr = xmlstr.replace("{http://s3.amazonaws.com/doc/2006-03-01/}", "")
|
||||
xmlstr = xmlstr.replace("{http://www.w3.org/2001/XMLSchema-instance}", "")
|
||||
if origin_str:
|
||||
xmlstr = xmlstr.replace(origin_str, replace_str)
|
||||
xmldict = eval(xmlstr)
|
||||
return xmldict
|
||||
|
||||
|
||||
# def get_id_from_xml(data, name):
|
||||
# """解析xml中的特定字段"""
|
||||
# tree = xml.dom.minidom.parseString(data)
|
||||
# root = tree.documentElement
|
||||
# result = root.getElementsByTagName(name)
|
||||
# # use childNodes to get a list, if has no child get itself
|
||||
# return result[0].childNodes[0].nodeValue
|
||||
|
||||
|
||||
def mapped(headers):
|
||||
"""S3到COS参数的一个映射"""
|
||||
_headers = dict()
|
||||
for i in headers:
|
||||
if i in maplist:
|
||||
if i == 'Metadata':
|
||||
for meta in headers[i]:
|
||||
_headers[meta] = headers[i][meta]
|
||||
else:
|
||||
_headers[maplist[i]] = headers[i]
|
||||
else:
|
||||
raise CosClientError('No Parameter Named ' + i + ' Please Check It')
|
||||
return _headers
|
||||
|
||||
|
||||
# def format_xml(data, root, lst=list(), parent_child=False):
|
||||
# """将dict转换为xml, xml_config是一个bytes"""
|
||||
# if parent_child:
|
||||
# xml_config = dicttoxml(data, item_func=lambda x: x[:-2], custom_root=root, attr_type=False)
|
||||
# else:
|
||||
# xml_config = dicttoxml(data, item_func=lambda x: x, custom_root=root, attr_type=False)
|
||||
# for i in lst:
|
||||
# xml_config = xml_config.replace(to_bytes(i + i), to_bytes(i))
|
||||
# return xml_config
|
||||
|
||||
|
||||
def format_xml(data, root):
|
||||
"""将dict转换为xml, xml_config是一个bytes"""
|
||||
input_dict = {root: data}
|
||||
xml_config = unparse(input_dict=input_dict).encode('utf-8')
|
||||
return xml_config
|
||||
|
||||
|
||||
def format_values(data):
|
||||
"""格式化headers和params中的values为bytes"""
|
||||
for i in data:
|
||||
data[i] = to_bytes(data[i])
|
||||
return data
|
||||
|
||||
|
||||
def format_endpoint(endpoint, region, module, EnableOldDomain, EnableInternalDomain):
|
||||
# 客户使用全球加速域名时,只会传endpoint不会传region。此时这样endpointCi和region同时为None,就会报错。
|
||||
if not endpoint and not region and module == u'cos.':
|
||||
raise CosClientError("Region or Endpoint is required not empty!")
|
||||
|
||||
"""格式化终端域名"""
|
||||
if endpoint:
|
||||
return to_unicode(endpoint)
|
||||
elif region:
|
||||
region = format_region(region, module, EnableOldDomain, EnableInternalDomain)
|
||||
if EnableOldDomain:
|
||||
return u"{region}.myqcloud.com".format(region=region)
|
||||
else:
|
||||
return u"{region}.tencentcos.cn".format(region=region)
|
||||
else:
|
||||
return None
|
||||
|
||||
def switch_hostname(host):
|
||||
if not host:
|
||||
raise CosClientError("Host is required not empty!")
|
||||
|
||||
# {bucket}-{appid}.cos.{region}.myqcloud.com
|
||||
if re.match(r'^([a-z0-9-]+-[0-9]+\.)(cos\.[a-z]+-[a-z]+(-[a-z]+)?(-1)?)\.(myqcloud\.com)$', host):
|
||||
host = host[:-len(".myqcloud.com")] + ".tencentcos.cn"
|
||||
|
||||
return host
|
||||
|
||||
def switch_hostname_for_url(url):
|
||||
if not url:
|
||||
raise CosClientError("Url is required not empty!")
|
||||
|
||||
url_parsed = urlparse(url)
|
||||
if url_parsed.hostname is not None:
|
||||
host = url_parsed.hostname
|
||||
new_host = switch_hostname(host)
|
||||
if host != new_host:
|
||||
new_url = url.replace(host, new_host)
|
||||
return new_url
|
||||
|
||||
return url
|
||||
|
||||
|
||||
def format_region(region, module, EnableOldDomain, EnableInternalDomain):
|
||||
"""格式化地域"""
|
||||
if not isinstance(region, string_types):
|
||||
raise CosClientError("region is not string type")
|
||||
if not region:
|
||||
raise CosClientError("region is required not empty!")
|
||||
region = to_unicode(region)
|
||||
if not re.match(r'^[A-Za-z0-9][A-Za-z0-9.\-]*[A-Za-z0-9]$', region):
|
||||
raise CosClientError("region format is illegal, only digit, letter and - is allowed!")
|
||||
if region.find(module) != -1:
|
||||
return region # 传入cos.ap-beijing-1这样显示加上cos.的region
|
||||
if region == u'cn-north' or region == u'cn-south' or region == u'cn-east' or region == u'cn-south-2' or region == u'cn-southwest' or region == u'sg':
|
||||
return region # 老域名不能加cos.
|
||||
# 支持v4域名映射到v5
|
||||
|
||||
# 转换为内部域名 (只有新域名才支持内部域名)
|
||||
if not EnableOldDomain and EnableInternalDomain and module == u'cos.':
|
||||
module = u'cos-internal.'
|
||||
|
||||
if region == u'cossh':
|
||||
return module + u'ap-shanghai'
|
||||
if region == u'cosgz':
|
||||
return module + u'ap-guangzhou'
|
||||
if region == 'cosbj':
|
||||
return module + u'ap-beijing'
|
||||
if region == 'costj':
|
||||
return module + u'ap-beijing-1'
|
||||
if region == u'coscd':
|
||||
return module + u'ap-chengdu'
|
||||
if region == u'cossgp':
|
||||
return module + u'ap-singapore'
|
||||
if region == u'coshk':
|
||||
return module + u'ap-hongkong'
|
||||
if region == u'cosca':
|
||||
return module + u'na-toronto'
|
||||
if region == u'cosger':
|
||||
return module + u'eu-frankfurt'
|
||||
|
||||
return module + region # 新域名加上cos.
|
||||
|
||||
|
||||
def format_bucket(bucket, appid):
|
||||
"""兼容新老bucket长短命名,appid为空默认为长命名,appid不为空则认为是短命名"""
|
||||
if not isinstance(bucket, string_types):
|
||||
raise CosClientError("bucket is not string")
|
||||
if not bucket:
|
||||
raise CosClientError("bucket is required not empty")
|
||||
if not (re.match(r'^[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9]$', bucket) or re.match('^[A-Za-z0-9]$', bucket)):
|
||||
raise CosClientError("bucket format is illegal, only digit, letter and - is allowed!")
|
||||
# appid为空直接返回bucket
|
||||
if not appid:
|
||||
return to_unicode(bucket)
|
||||
if not isinstance(appid, string_types):
|
||||
raise CosClientError("appid is not string")
|
||||
bucket = to_unicode(bucket)
|
||||
appid = to_unicode(appid)
|
||||
# appid不为空,检查是否以-appid结尾
|
||||
if bucket.endswith(u"-" + appid):
|
||||
return bucket
|
||||
return bucket + u"-" + appid
|
||||
|
||||
|
||||
def path_simplify_check(path):
|
||||
"""将path按照posix路径语义合并后,如果结果为空或'/'则抛异常"""
|
||||
path = to_unicode(path)
|
||||
stack = list()
|
||||
tokens = path.split(u'/')
|
||||
for token in tokens:
|
||||
if token == u'..':
|
||||
if stack:
|
||||
stack.pop()
|
||||
elif token and token != u'.':
|
||||
stack.append(token)
|
||||
path = u'/' + u'/'.join(stack)
|
||||
if path == u'/':
|
||||
raise CosClientError("GetObject Key is invalid")
|
||||
|
||||
|
||||
def format_path(path):
|
||||
"""检查path是否合法,格式化path"""
|
||||
if not isinstance(path, string_types):
|
||||
raise CosClientError("key is not string")
|
||||
if not path:
|
||||
raise CosClientError("Key is required not empty")
|
||||
path = to_unicode(path)
|
||||
if path[0] == u'/':
|
||||
path = path[1:]
|
||||
# 提前对path进行encode
|
||||
path = quote(to_bytes(path), b'/-_.~')
|
||||
return path
|
||||
|
||||
|
||||
def get_copy_source_info(CopySource, EnableOldDomain, EnableInternalDomain):
|
||||
"""获取拷贝源的所有信息"""
|
||||
appid = u""
|
||||
versionid = u""
|
||||
region = u""
|
||||
endpoint = u""
|
||||
if 'Appid' in CopySource:
|
||||
appid = CopySource['Appid']
|
||||
if 'Bucket' in CopySource:
|
||||
bucket = CopySource['Bucket']
|
||||
bucket = format_bucket(bucket, appid)
|
||||
else:
|
||||
raise CosClientError('CopySource Need Parameter Bucket')
|
||||
if 'Region' in CopySource:
|
||||
region = CopySource['Region']
|
||||
if 'Endpoint' in CopySource:
|
||||
endpoint = CopySource['Endpoint']
|
||||
endpoint = format_endpoint(endpoint, region, u'cos.', EnableOldDomain, EnableInternalDomain)
|
||||
if 'Key' in CopySource:
|
||||
path = to_unicode(CopySource['Key'])
|
||||
if path and path[0] == '/':
|
||||
path = path[1:]
|
||||
else:
|
||||
raise CosClientError('CopySource Need Parameter Key')
|
||||
if 'VersionId' in CopySource:
|
||||
versionid = to_unicode(CopySource['VersionId'])
|
||||
return bucket, path, endpoint, versionid
|
||||
|
||||
|
||||
def gen_copy_source_url(CopySource, EnableOldDomain, EnableInternalDomain):
|
||||
"""拼接拷贝源url"""
|
||||
bucket, path, endpoint, versionid = get_copy_source_info(CopySource, EnableOldDomain, EnableInternalDomain)
|
||||
path = format_path(path)
|
||||
if versionid != u'':
|
||||
path = path + u'?versionId=' + versionid
|
||||
url = u"{bucket}.{endpoint}/{path}".format(
|
||||
bucket=bucket,
|
||||
endpoint=endpoint,
|
||||
path=path
|
||||
)
|
||||
return url
|
||||
|
||||
|
||||
def gen_copy_source_range(begin_range, end_range):
|
||||
"""拼接bytes=begin-end形式的字符串"""
|
||||
range = u"bytes={first}-{end}".format(
|
||||
first=to_unicode(begin_range),
|
||||
end=to_unicode(end_range)
|
||||
)
|
||||
return range
|
||||
|
||||
|
||||
def get_file_like_object_length(data):
|
||||
try:
|
||||
total_length = os.fstat(data.fileno()).st_size
|
||||
except IOError:
|
||||
if hasattr(data, '__len__'):
|
||||
total_length = len(data)
|
||||
else:
|
||||
# support BytesIO file-like object
|
||||
total_length = len(data.getvalue())
|
||||
try:
|
||||
current_position = data.tell()
|
||||
except IOError:
|
||||
current_position = 0
|
||||
content_len = total_length - current_position
|
||||
return content_len
|
||||
|
||||
|
||||
def check_object_content_length(data):
|
||||
"""put_object接口和upload_part接口的文件大小不允许超过5G"""
|
||||
content_len = 0
|
||||
if isinstance(data, text_type) or isinstance(data, binary_type):
|
||||
content_len = len(to_bytes(data))
|
||||
elif hasattr(data, 'fileno') and hasattr(data, 'tell'):
|
||||
content_len = get_file_like_object_length(data)
|
||||
else:
|
||||
# can not get the content-length, use chunked to upload the file
|
||||
pass
|
||||
if content_len > SINGLE_UPLOAD_LENGTH:
|
||||
raise CosClientError('The object size you upload can not be larger than 5GB in put_object or upload_part')
|
||||
return None
|
||||
|
||||
|
||||
def format_dict(data, key_lst):
|
||||
"""转换返回dict中的可重复字段为list"""
|
||||
if not (isinstance(data, dict) and isinstance(key_lst, list)):
|
||||
return data
|
||||
for key in key_lst:
|
||||
# 将dict转为list,保持一致
|
||||
if key in data and (isinstance(data[key], dict) or isinstance(data[key], string_types)):
|
||||
lst = []
|
||||
lst.append(data[key])
|
||||
data[key] = lst
|
||||
if key in data and data[key] is None:
|
||||
lst = []
|
||||
data[key] = lst
|
||||
return data
|
||||
|
||||
|
||||
def format_dict_or_list(data, key_lst):
|
||||
"""转换返回dict或list中的可重复字段为list"""
|
||||
if not ((isinstance(data, list) or isinstance(data, dict)) and isinstance(key_lst, list)):
|
||||
return data
|
||||
if isinstance(data, dict):
|
||||
return format_dict(data, key_lst)
|
||||
|
||||
for data_item in data:
|
||||
format_dict(data_item, key_lst)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def decode_result(data, key_lst, multi_key_list):
|
||||
"""decode结果中的字段"""
|
||||
for key in key_lst:
|
||||
if key in data and data[key]:
|
||||
data[key] = unquote(data[key])
|
||||
for multi_key in multi_key_list:
|
||||
if multi_key[0] in data:
|
||||
for item in data[multi_key[0]]:
|
||||
if multi_key[1] in item and item[multi_key[1]]:
|
||||
item[multi_key[1]] = unquote(item[multi_key[1]])
|
||||
return data
|
||||
|
||||
|
||||
def get_date(yy, mm, dd):
|
||||
"""获取lifecycle中Date字段"""
|
||||
date_str = datetime(yy, mm, dd).isoformat()
|
||||
final_date_str = date_str + '+08:00'
|
||||
return final_date_str
|
||||
|
||||
|
||||
def parse_object_canned_acl(result_acl, rsp_headers):
|
||||
"""根据ACL返回的body信息,以及default头部来判断CannedACL"""
|
||||
if "x-cos-acl" in rsp_headers and rsp_headers["x-cos-acl"] == "default":
|
||||
return "default"
|
||||
public_read = {'Grantee': {'Type': 'Group', 'URI': 'http://cam.qcloud.com/groups/global/AllUsers'},
|
||||
'Permission': 'READ'}
|
||||
if 'AccessControlList' in result_acl and result_acl['AccessControlList'] is not None and 'Grant' in result_acl['AccessControlList']:
|
||||
if public_read in result_acl['AccessControlList']['Grant']:
|
||||
return "public-read"
|
||||
return "private"
|
||||
|
||||
|
||||
def parse_bucket_canned_acl(result_acl):
|
||||
"""根据ACL返回的body信息来判断Bucket CannedACL"""
|
||||
public_read = {'Grantee': {'Type': 'Group', 'URI': 'http://cam.qcloud.com/groups/global/AllUsers'},
|
||||
'Permission': 'READ'}
|
||||
public_write = {'Grantee': {'Type': 'Group', 'URI': 'http://cam.qcloud.com/groups/global/AllUsers'},
|
||||
'Permission': 'WRITE'}
|
||||
if 'AccessControlList' in result_acl and result_acl['AccessControlList'] is not None and 'Grant' in result_acl['AccessControlList']:
|
||||
if public_read in result_acl['AccessControlList']['Grant']:
|
||||
if public_write in result_acl['AccessControlList']['Grant']:
|
||||
return "public-read-write"
|
||||
return "public-read"
|
||||
return "private"
|
||||
|
||||
|
||||
def client_can_retry(file_position, **kwargs):
|
||||
"""如果客户端请求中不包含data则可以重试,以及判断包含data的请求是否可以重试"""
|
||||
if 'data' not in kwargs:
|
||||
return True
|
||||
body = kwargs['data']
|
||||
if isinstance(body, text_type) or isinstance(body, binary_type):
|
||||
return True
|
||||
if file_position is not None and hasattr(body, 'tell') and hasattr(body, 'seek') and hasattr(body, 'read'):
|
||||
try:
|
||||
kwargs['data'].seek(file_position)
|
||||
return True
|
||||
except Exception as ioe:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
class CiDetectType():
|
||||
"""ci内容设备的类型设置,可与操作设多个"""
|
||||
PORN = 1
|
||||
TERRORIST = 2
|
||||
POLITICS = 4
|
||||
ADS = 8
|
||||
ILLEGAL = 16
|
||||
ABUSE = 32
|
||||
TEENAGER = 64
|
||||
|
||||
@staticmethod
|
||||
def get_detect_type_str(DetectType):
|
||||
"""获取审核的文字描述,这里只支持ci域名的入参,cos的跟这个还不一样"""
|
||||
detect_type = ''
|
||||
if DetectType & CiDetectType.PORN > 0:
|
||||
detect_type += 'Porn'
|
||||
if DetectType & CiDetectType.TERRORIST > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Terrorism'
|
||||
if DetectType & CiDetectType.POLITICS > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Politics'
|
||||
if DetectType & CiDetectType.ADS > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Ads'
|
||||
if DetectType & CiDetectType.ILLEGAL > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Illegal'
|
||||
if DetectType & CiDetectType.ABUSE > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Abuse'
|
||||
if DetectType & CiDetectType.TEENAGER > 0:
|
||||
if len(detect_type) > 0:
|
||||
detect_type += ','
|
||||
detect_type += 'Teenager'
|
||||
|
||||
return detect_type
|
||||
|
||||
|
||||
class ProgressCallback():
|
||||
def __init__(self, file_size, progress_callback):
|
||||
self.__lock = threading.Lock()
|
||||
self.__finished_size = 0
|
||||
self.__file_size = file_size
|
||||
self.__progress_callback = progress_callback
|
||||
|
||||
def report(self, size):
|
||||
with self.__lock:
|
||||
self.__finished_size += size
|
||||
self.__progress_callback(self.__finished_size, self.__file_size)
|
||||
@@ -0,0 +1,182 @@
|
||||
# -*- coding=utf-8
|
||||
|
||||
import logging
|
||||
from qcloud_cos import CosS3Client
|
||||
from qcloud_cos.crypto import RSAProvider
|
||||
from qcloud_cos.crypto import MetaHandle
|
||||
from qcloud_cos.crypto import DataDecryptAdapter
|
||||
from .cos_exception import CosClientError
|
||||
from .cos_comm import *
|
||||
from .cos_auth import CosS3Auth
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CosEncryptionClient(CosS3Client):
|
||||
"""cos支持加密的客户端,封装相应请求"""
|
||||
|
||||
def __init__(self, conf, provider, retry=1, session=None):
|
||||
"""初始化client对象
|
||||
|
||||
:param conf(CosConfig): 用户的配置.
|
||||
:param provider(BaseProvider): 客户端主密钥加密类
|
||||
:param retry(int): 失败重试的次数.
|
||||
:param session(object): http session.
|
||||
"""
|
||||
super(CosEncryptionClient, self).__init__(conf, retry, session)
|
||||
self.provider = provider
|
||||
|
||||
def put_object(self, Bucket, Body, Key, EnableMD5=False, **kwargs):
|
||||
"""单文件加密上传接口,适用于小文件,最大不得超过5GB
|
||||
|
||||
:param Bucket(string): 存储桶名称.
|
||||
:param Body(file|string): 上传的文件内容,类型为文件流或字节流.
|
||||
:param Key(string): COS路径.
|
||||
:param EnableMD5(bool): 是否需要SDK计算Content-MD5,打开此开关会增加上传耗时.
|
||||
:kwargs(dict): 设置上传的headers.
|
||||
:return(dict): 上传成功返回的结果,包含ETag等信息.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
provider = RSAProvider()
|
||||
client = CosEncryptionClient(config, provider)
|
||||
# 上传本地文件到cos
|
||||
with open('test.txt', 'rb') as fp:
|
||||
response = client.put_object(
|
||||
Bucket='bucket',
|
||||
Body=fp,
|
||||
Key='test.txt'
|
||||
)
|
||||
print (response['ETag'])
|
||||
"""
|
||||
encrypt_key, encrypt_start = self.provider.init_data_cipher()
|
||||
meta_handle = MetaHandle(encrypt_key, encrypt_start)
|
||||
kwargs = meta_handle.set_object_meta(kwargs)
|
||||
data = self.provider.make_data_encrypt_adapter(Body)
|
||||
response = super(CosEncryptionClient, self).put_object(Bucket, data, Key, EnableMD5, **kwargs)
|
||||
return response
|
||||
|
||||
def get_object(self, Bucket, Key, **kwargs):
|
||||
"""单文件加密下载接口
|
||||
|
||||
:param Bucket(string): 存储桶名称.
|
||||
:param Key(string): COS路径.
|
||||
:param kwargs(dict): 设置下载的headers.
|
||||
:return(dict): 下载成功返回的结果,包含Body对应的StreamBody,可以获取文件流或下载文件到本地.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
provider = RSAProvider()
|
||||
client = CosEncryptionClient(config, provider)
|
||||
# 下载cos上的文件到本地
|
||||
response = client.get_object(
|
||||
Bucket='bucket',
|
||||
Key='test.txt'
|
||||
)
|
||||
response['Body'].get_stream_to_file('local_file.txt')
|
||||
"""
|
||||
response = self.head_object(Bucket, Key, **kwargs)
|
||||
meta_handle = MetaHandle()
|
||||
encrypt_key, encrypt_start = meta_handle.get_object_meta(response)
|
||||
|
||||
headers = mapped(kwargs)
|
||||
offset = 0
|
||||
real_start = 0
|
||||
if 'Range' in headers:
|
||||
read_range = headers['Range']
|
||||
read_range = read_range.replace('bytes=', '').strip().split('-')
|
||||
if len(read_range) != 2:
|
||||
raise CosClientError('key:Range is wrong format, except first-last')
|
||||
|
||||
real_start = self.provider.adjust_read_offset(int(read_range[0]))
|
||||
headers['Range'] = 'bytes=' + str(real_start) + '-' + read_range[1]
|
||||
offset = int(read_range[0]) - real_start
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
if 'versionId' in headers:
|
||||
params['versionId'] = headers['versionId']
|
||||
del headers['versionId']
|
||||
params = format_values(params)
|
||||
|
||||
url = self._conf.uri(bucket=Bucket, path=Key)
|
||||
logger.info("get object, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
bucket=Bucket,
|
||||
stream=True,
|
||||
auth=CosS3Auth(self._conf, Key, params=params),
|
||||
params=params,
|
||||
headers=headers)
|
||||
|
||||
self.provider.init_data_cipter_by_user(encrypt_key, encrypt_start, real_start)
|
||||
response['Body'] = self.provider.make_data_decrypt_adapter(rt, offset)
|
||||
return response
|
||||
|
||||
def create_multipart_upload(self, Bucket, Key, **kwargs):
|
||||
"""创建分块上传,适用于大文件上传
|
||||
|
||||
:param Bucket(string): 存储桶名称.
|
||||
:param Key(string): COS路径.
|
||||
:param kwargs(dict): 设置请求headers.
|
||||
:return(dict): 初始化分块上传返回的结果,包含UploadId等信息.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
provider = RSAProvider()
|
||||
client = CosEncryptionClient(config, provider)
|
||||
# 创建分块上传
|
||||
response = client.create_multipart_upload(
|
||||
Bucket='bucket',
|
||||
Key='test.txt'
|
||||
)
|
||||
"""
|
||||
encrypt_key, encrypt_start = self.provider.init_data_cipher()
|
||||
meta_handle = MetaHandle(encrypt_key, encrypt_start)
|
||||
kwargs = meta_handle.set_object_meta(kwargs)
|
||||
response = super(CosEncryptionClient, self).create_multipart_upload(Bucket, Key, **kwargs)
|
||||
return response
|
||||
|
||||
def upload_part(self, Bucket, Key, Body, PartNumber, UploadId, EnableMD5=False, **kwargs):
|
||||
"""上传分块,单个大小不得超过5GB
|
||||
|
||||
:param Bucket(string): 存储桶名称.
|
||||
:param Key(string): COS路径.
|
||||
:param Body(file|string): 上传分块的内容,可以为文件流或者字节流.
|
||||
:param PartNumber(int): 上传分块的编号.
|
||||
:param UploadId(string): 分块上传创建的UploadId.
|
||||
:param kwargs(dict): 设置请求headers.
|
||||
:param EnableMD5(bool): 是否需要SDK计算Content-MD5,打开此开关会增加上传耗时.
|
||||
:return(dict): 上传成功返回的结果,包含单个分块ETag等信息.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
provider = RSAProvider()
|
||||
client = CosEncryptionClient(config, provider)
|
||||
# 分块上传
|
||||
with open('test.txt', 'rb') as fp:
|
||||
data = fp.read(1024*1024)
|
||||
response = client.upload_part(
|
||||
Bucket='bucket',
|
||||
Body=data,
|
||||
Key='test.txt'
|
||||
)
|
||||
"""
|
||||
data = self.provider.make_data_encrypt_adapter(Body)
|
||||
response = super(CosEncryptionClient, self).upload_part(Bucket, Key, data, PartNumber, UploadId,
|
||||
EnableMD5=False, **kwargs)
|
||||
return response
|
||||
@@ -0,0 +1,102 @@
|
||||
# -*- coding=utf-8
|
||||
|
||||
import xml.dom.minidom
|
||||
from requests.structures import CaseInsensitiveDict
|
||||
|
||||
|
||||
class CosException(Exception):
|
||||
def __init__(self, message):
|
||||
self._message = message
|
||||
|
||||
def __str__(self):
|
||||
return str(self._message)
|
||||
|
||||
|
||||
def digest_xml(data):
|
||||
msg = dict()
|
||||
try:
|
||||
tree = xml.dom.minidom.parseString(data)
|
||||
root = tree.documentElement
|
||||
|
||||
result = root.getElementsByTagName('Code')
|
||||
msg['code'] = result[0].childNodes[0].nodeValue
|
||||
|
||||
result = root.getElementsByTagName('Message')
|
||||
msg['message'] = result[0].childNodes[0].nodeValue
|
||||
|
||||
result = root.getElementsByTagName('Resource')
|
||||
msg['resource'] = result[0].childNodes[0].nodeValue
|
||||
|
||||
result = root.getElementsByTagName('RequestId')
|
||||
msg['requestid'] = result[0].childNodes[0].nodeValue
|
||||
|
||||
result = root.getElementsByTagName('TraceId')
|
||||
if result:
|
||||
msg['traceid'] = result[0].childNodes[0].nodeValue
|
||||
else:
|
||||
msg['traceid'] = 'Unknown'
|
||||
return msg
|
||||
except Exception as e:
|
||||
return "Response Error Msg Is INVALID"
|
||||
|
||||
|
||||
class CosClientError(CosException):
|
||||
"""Client端错误,如timeout"""
|
||||
|
||||
def __init__(self, message):
|
||||
CosException.__init__(self, message)
|
||||
|
||||
|
||||
class CosServiceError(CosException):
|
||||
"""COS Server端错误,可以获取特定的错误信息"""
|
||||
|
||||
def __init__(self, method, message, status_code):
|
||||
CosException.__init__(self, message)
|
||||
if isinstance(message, dict) or isinstance(message, CaseInsensitiveDict):
|
||||
self._origin_msg = ''
|
||||
self._digest_msg = message
|
||||
else:
|
||||
self._origin_msg = message
|
||||
self._digest_msg = digest_xml(message)
|
||||
self._status_code = status_code
|
||||
|
||||
def __str__(self):
|
||||
return str(self._digest_msg)
|
||||
|
||||
def get_origin_msg(self):
|
||||
"""获取原始的XML格式错误信息"""
|
||||
return self._origin_msg
|
||||
|
||||
def get_digest_msg(self):
|
||||
"""获取经过处理的dict格式的错误信息"""
|
||||
return self._digest_msg
|
||||
|
||||
def get_status_code(self):
|
||||
"""获取http error code"""
|
||||
return self._status_code
|
||||
|
||||
def get_error_code(self):
|
||||
"""获取COS定义的错误码描述,服务器返回错误信息格式出错时,返回空 """
|
||||
if isinstance(self._digest_msg, dict):
|
||||
return self._digest_msg['code']
|
||||
return "Unknown"
|
||||
|
||||
def get_error_msg(self):
|
||||
if isinstance(self._digest_msg, dict):
|
||||
return self._digest_msg['message']
|
||||
return "Unknown"
|
||||
|
||||
def get_resource_location(self):
|
||||
if isinstance(self._digest_msg, dict):
|
||||
return self._digest_msg['resource']
|
||||
return "Unknown"
|
||||
|
||||
def get_trace_id(self):
|
||||
if isinstance(self._digest_msg, dict):
|
||||
return self._digest_msg['traceid']
|
||||
return "Unknown"
|
||||
|
||||
def get_request_id(self):
|
||||
if isinstance(self._digest_msg, dict):
|
||||
return self._digest_msg['requestid']
|
||||
return "Unknown"
|
||||
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from threading import Thread
|
||||
from logging import getLogger
|
||||
from six.moves.queue import Queue
|
||||
from threading import Lock
|
||||
import gc
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class WorkerThread(Thread):
|
||||
def __init__(self, task_queue, *args, **kwargs):
|
||||
super(WorkerThread, self).__init__(*args, **kwargs)
|
||||
|
||||
self._task_queue = task_queue
|
||||
self._succ_task_num = 0
|
||||
self._fail_task_num = 0
|
||||
self._ret = list()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
func, args, kwargs = self._task_queue.get()
|
||||
# 判断线程是否需要退出
|
||||
if func is None:
|
||||
return
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
self._succ_task_num += 1
|
||||
self._ret.append(ret)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
self._fail_task_num += 1
|
||||
if hasattr(e, '_message') and e._message:
|
||||
self._ret.append(e._message)
|
||||
elif hasattr(e, 'message') and e.message:
|
||||
self._ret.append(e.message)
|
||||
else:
|
||||
self._ret.append('meet some exception')
|
||||
finally:
|
||||
self._task_queue.task_done()
|
||||
|
||||
def get_result(self):
|
||||
return self._succ_task_num, self._fail_task_num, self._ret
|
||||
|
||||
|
||||
class SimpleThreadPool:
|
||||
|
||||
def __init__(self, num_threads=5, num_queue=0):
|
||||
self._num_threads = num_threads
|
||||
self._queue = Queue(num_queue)
|
||||
self._lock = Lock()
|
||||
self._active = False
|
||||
self._workers = list()
|
||||
self._finished = False
|
||||
|
||||
def add_task(self, func, *args, **kwargs):
|
||||
if not self._active:
|
||||
with self._lock:
|
||||
if not self._active:
|
||||
self._workers = []
|
||||
self._active = True
|
||||
|
||||
for i in range(self._num_threads):
|
||||
w = WorkerThread(self._queue)
|
||||
self._workers.append(w)
|
||||
w.start()
|
||||
|
||||
self._queue.put((func, args, kwargs))
|
||||
|
||||
def wait_completion(self):
|
||||
self._queue.join()
|
||||
self._finished = True
|
||||
# 已经结束的任务, 需要将线程都退出, 防止卡死
|
||||
for i in range(self._num_threads):
|
||||
self._queue.put((None, None, None))
|
||||
|
||||
self._active = False
|
||||
|
||||
def get_result(self):
|
||||
assert self._finished
|
||||
detail = [worker.get_result() for worker in self._workers]
|
||||
succ_all = all([tp[1] == 0 for tp in detail])
|
||||
return {'success_all': succ_all, 'detail': detail}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
|
||||
# pool = SimpleThreadPool(2)
|
||||
|
||||
# def task_sleep(x):
|
||||
# from time import sleep
|
||||
# sleep(x)
|
||||
# return 'hello, sleep %d seconds' % x
|
||||
|
||||
# def raise_exception():
|
||||
# raise ValueError("Pa! Exception!")
|
||||
|
||||
# for i in range(1000):
|
||||
# pool.add_task(task_sleep, 0.001)
|
||||
# print(i)
|
||||
# pool.add_task(task_sleep, 0)
|
||||
# pool.add_task(task_sleep, 0)
|
||||
# pool.add_task(raise_exception)
|
||||
# pool.add_task(raise_exception)
|
||||
|
||||
# pool.wait_completion()
|
||||
# print(pool.get_result())
|
||||
# [(1, 0, ['hello, sleep 5 seconds']), (2, 1, ['hello, sleep 2 seconds', 'hello, sleep 3 seconds', ValueError('Pa! Exception!',)])]
|
||||
429
venv_prod/lib/python3.12/site-packages/qcloud_cos/crypto.py
Normal file
429
venv_prod/lib/python3.12/site-packages/qcloud_cos/crypto.py
Normal file
@@ -0,0 +1,429 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
import copy
|
||||
import base64
|
||||
import struct
|
||||
from .cos_comm import *
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto import Random
|
||||
from Crypto.Util import Counter
|
||||
from Crypto.Cipher import PKCS1_OAEP, PKCS1_v1_5
|
||||
from .cos_exception import CosClientError
|
||||
from .streambody import StreamBody
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_AES_CTR_COUNTER_BITS_LENGTH = 8 * 16
|
||||
_AES_256_KEY_SIZE = 32
|
||||
|
||||
|
||||
def random_key(key_len):
|
||||
return Random.new().read(key_len)
|
||||
|
||||
|
||||
def random_iv():
|
||||
iv = Random.new().read(16)
|
||||
return iv
|
||||
|
||||
|
||||
def iv_to_big_int(iv):
|
||||
iv_pair = struct.unpack(">QQ", iv)
|
||||
iv_int = iv_pair[0] << 64 | iv_pair[1]
|
||||
return iv_int
|
||||
|
||||
|
||||
class AESCTRCipher(object):
|
||||
"""数据加密类,用于加密用户数据"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化"""
|
||||
self.__block_size_len = AES.block_size
|
||||
self.__cipher = None
|
||||
self.__key_len = _AES_256_KEY_SIZE
|
||||
|
||||
def new_cipher(self, key, start, offset=0):
|
||||
"""初始化AES加解密对象
|
||||
|
||||
:param key(string): 对称密钥
|
||||
:param start(int): 对称加密初始随机值
|
||||
:param offset(int): 主要用于解密,想要解密文本的偏移,必须为16的整数倍
|
||||
"""
|
||||
block_index_offset = self.__calc_offset(offset)
|
||||
new_start = start + block_index_offset
|
||||
my_counter = Counter.new(_AES_CTR_COUNTER_BITS_LENGTH, initial_value=new_start)
|
||||
self.__cipher = AES.new(key, AES.MODE_CTR, counter=my_counter)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""加密数据
|
||||
|
||||
:param plaintext(string): 需要加密的数据
|
||||
"""
|
||||
if self.__cipher is None:
|
||||
raise CosClientError('cipher is not initialized')
|
||||
return self.__cipher.encrypt(plaintext)
|
||||
|
||||
def decrypt(self, plaintext):
|
||||
"""解密数据
|
||||
|
||||
:param plaintext(string): 需要解密的数据,数据的起始位置必须为16的整数倍
|
||||
"""
|
||||
if self.__cipher is None:
|
||||
raise CosClientError('cipher is not initialized')
|
||||
return self.__cipher.decrypt(plaintext)
|
||||
|
||||
# offset 必须为block_size的整数倍
|
||||
def __calc_offset(self, offset):
|
||||
"""通过文本偏移计算counter的偏移
|
||||
|
||||
:param offset(int): 文本偏移
|
||||
"""
|
||||
if not self.__is_block_aligned(offset):
|
||||
raise CosClientError('offset is not align to encrypt block')
|
||||
return offset // self.__block_size_len
|
||||
|
||||
def __is_block_aligned(self, offset):
|
||||
"""判断文本偏移是否是block_size对齐
|
||||
|
||||
:param offset(int): 文本偏移
|
||||
"""
|
||||
if offset is None:
|
||||
offset = 0
|
||||
return 0 == offset % self.__block_size_len
|
||||
|
||||
def adjust_read_offset(self, offset):
|
||||
"""用于调整读取的offset为block_size对齐"""
|
||||
|
||||
if offset:
|
||||
offset = (offset // self.__block_size_len) * self.__block_size_len
|
||||
return offset
|
||||
|
||||
def get_key(self):
|
||||
"""获取随机密钥"""
|
||||
return random_key(self.__key_len)
|
||||
|
||||
def get_counter_iv(self):
|
||||
"""获取对称加密初始随机值"""
|
||||
return random_iv()
|
||||
|
||||
|
||||
class RSAKeyPair:
|
||||
"""封装有公钥和私钥"""
|
||||
|
||||
def __init__(self, public_key, private_key):
|
||||
self.publick_key = public_key
|
||||
self.private_key = private_key
|
||||
|
||||
|
||||
class RSAKeyPairPath:
|
||||
"""封装有公钥和私钥的路径"""
|
||||
|
||||
def __init__(self, public_key_path, private_key_path):
|
||||
self.public_key_path = public_key_path
|
||||
self.private_key_path = private_key_path
|
||||
|
||||
|
||||
class BaseProvider(object):
|
||||
"""客户端主密钥加密基类"""
|
||||
|
||||
def __init__(self, cipher):
|
||||
"""初始化
|
||||
:param cipher(an AES object): 数据加解密类
|
||||
"""
|
||||
self.data_cipher = cipher
|
||||
|
||||
def get_data_key(self):
|
||||
"""随机获取数据加解密密钥"""
|
||||
return self.data_cipher.get_key()
|
||||
|
||||
@abstractmethod
|
||||
def init_data_cipher(self):
|
||||
"""初始化cipher"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
|
||||
"""根据密钥初始化cipher"""
|
||||
pass
|
||||
|
||||
def adjust_read_offset(self, start):
|
||||
"""用于调整读取的offset为block_size对齐"""
|
||||
return self.data_cipher.adjust_read_offset(start)
|
||||
|
||||
def make_data_encrypt_adapter(self, stream):
|
||||
"""创建数据流加密适配器"""
|
||||
size = 0
|
||||
if hasattr(stream, '__len__'):
|
||||
size = len(stream)
|
||||
elif hasattr(stream, 'tell') and hasattr(stream, 'seek'):
|
||||
current = stream.tell()
|
||||
stream.seek(0, os.SEEK_END)
|
||||
size = stream.tell()
|
||||
stream.seek(current, os.SEEK_SET)
|
||||
else:
|
||||
return None
|
||||
return DataEncryptAdapter(stream, size, copy.copy(self.data_cipher))
|
||||
|
||||
def make_data_decrypt_adapter(self, rt, offset):
|
||||
"""创建数据流解密适配器"""
|
||||
return DataDecryptAdapter(rt, copy.copy(self.data_cipher), offset)
|
||||
|
||||
|
||||
class RSAProvider(BaseProvider):
|
||||
"""客户端非对称主密钥加密类"""
|
||||
|
||||
def __init__(self, key_pair_info=None, cipher=AESCTRCipher(), passphrase=None):
|
||||
"""初始化"""
|
||||
super(RSAProvider, self).__init__(cipher=cipher)
|
||||
|
||||
default_rsa_dir = os.path.expanduser('~/.cos_local_rsa')
|
||||
default_public_key_path = os.path.join(default_rsa_dir, '.public_key.pem')
|
||||
default_private_key_path = os.path.join(default_rsa_dir, '.private_key.pem')
|
||||
self.__encrypt_obj = None
|
||||
self.__decrypt_obj = None
|
||||
self.__data_key = None
|
||||
self.__data_iv = None
|
||||
|
||||
if isinstance(key_pair_info, RSAKeyPair):
|
||||
self.__encrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair_info.publick_key, passphrase=passphrase))
|
||||
self.__decrypt_obj = PKCS1_v1_5.new(RSA.importKey(key_pair_info.private_key, passphrase=passphrase))
|
||||
elif isinstance(key_pair_info, RSAKeyPairPath):
|
||||
self.__encrypt_obj, self.__decrypt_obj = self.__get_key_by_path(key_pair_info.public_key_path,
|
||||
key_pair_info.private_key_path, passphrase)
|
||||
else:
|
||||
logger.info('key_pair_info is None, try to get key from default path')
|
||||
if os.path.exists(default_private_key_path) and os.path.exists(default_public_key_path):
|
||||
self.__encrypt_obj, self.__decrypt_obj = self.__get_key_by_path(default_public_key_path,
|
||||
default_private_key_path, passphrase)
|
||||
|
||||
# 为用户自动创建rsa
|
||||
if self.__encrypt_obj is None and self.__decrypt_obj is None:
|
||||
logger.warning('fail to get rsa key, will generate key')
|
||||
private_key = RSA.generate(2048)
|
||||
public_key = private_key.publickey()
|
||||
|
||||
self.__encrypt_obj = PKCS1_OAEP.new(public_key)
|
||||
self.__decrypt_obj = PKCS1_OAEP.new(private_key)
|
||||
|
||||
if not os.path.exists(default_rsa_dir):
|
||||
os.makedirs(default_rsa_dir)
|
||||
|
||||
with open(default_private_key_path, 'wb') as f:
|
||||
f.write(private_key.exportKey(passphrase=passphrase))
|
||||
|
||||
with open(default_public_key_path, 'wb') as f:
|
||||
f.write(public_key.exportKey(passphrase=passphrase))
|
||||
|
||||
@staticmethod
|
||||
def get_rsa_key_pair(public_key, private_key):
|
||||
"""开放给用户,用于生成RSAKeyPair"""
|
||||
if public_key is None or private_key is None:
|
||||
raise CosClientError('public_key or private_key is not allowed to be None !!!')
|
||||
return RSAKeyPair(public_key, private_key)
|
||||
|
||||
@staticmethod
|
||||
def get_rsa_key_pair_path(public_key_path, private_key_path):
|
||||
"""开放给用户,用于生成RSAKeyPairPath"""
|
||||
if public_key_path is None or private_key_path is None:
|
||||
raise CosClientError('public_key or private_key is not allowed to be None !!!')
|
||||
return RSAKeyPairPath(public_key_path, private_key_path)
|
||||
|
||||
def __get_key_by_path(self, public_path=None, private_path=None, passphrase=None):
|
||||
"""用于从提供的路径中获取公钥和私钥"""
|
||||
if public_path is None or private_path is None:
|
||||
return None, None
|
||||
|
||||
encrypt_obj, decrypt_obj = None, None
|
||||
|
||||
if os.path.exists(public_path) and os.path.exists(private_path):
|
||||
with open(public_path, 'rb') as f:
|
||||
encrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
|
||||
|
||||
with open(private_path, 'rb') as f:
|
||||
decrypt_obj = PKCS1_OAEP.new(RSA.importKey(f.read(), passphrase=passphrase))
|
||||
|
||||
return encrypt_obj, decrypt_obj
|
||||
|
||||
def init_data_cipher(self):
|
||||
"""初始化cipher"""
|
||||
encrypt_key = None
|
||||
encrypt_iv = None
|
||||
self.__data_key = self.get_data_key()
|
||||
self.__data_iv = self.data_cipher.get_counter_iv()
|
||||
start = iv_to_big_int(self.__data_iv)
|
||||
self.data_cipher.new_cipher(self.__data_key, start)
|
||||
encrypt_key = self.__encrypt_obj.encrypt(self.__data_key)
|
||||
encrypt_iv = self.__encrypt_obj.encrypt(self.__data_iv)
|
||||
return encrypt_key, encrypt_iv
|
||||
|
||||
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
|
||||
"""根据密钥初始化cipher"""
|
||||
self.__data_key = self.__decrypt_obj.decrypt(encrypt_key)
|
||||
self.__data_iv = self.__decrypt_obj.decrypt(encrypt_iv)
|
||||
start = iv_to_big_int(self.__data_iv)
|
||||
self.data_cipher.new_cipher(self.__data_key, start, offset)
|
||||
|
||||
|
||||
class AESProvider(BaseProvider):
|
||||
"""客户端对称主密钥加密类"""
|
||||
|
||||
def __init__(self, aes_key=None, aes_key_path=None, cipher=AESCTRCipher()):
|
||||
"""初始化"""
|
||||
super(AESProvider, self).__init__(cipher=cipher)
|
||||
self.__ed_obj = None
|
||||
self.__data_key = None
|
||||
self.__data_iv = None
|
||||
self.__my_counter = Counter.new(_AES_CTR_COUNTER_BITS_LENGTH, initial_value=0)
|
||||
self.__aes_key = aes_key
|
||||
self.__aes_key_path = aes_key_path
|
||||
|
||||
def init_ed_obj(self):
|
||||
default_aes_dir = os.path.expanduser('~/.cos_local_aes')
|
||||
default_key_path = os.path.join(default_aes_dir, '.aes_key.pem')
|
||||
if self.__aes_key:
|
||||
aes_key = to_bytes(base64.b64decode(to_bytes(self.__aes_key)))
|
||||
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
|
||||
elif self.__aes_key_path:
|
||||
if os.path.exists(self.__aes_key_path):
|
||||
with open(self.__aes_key_path, 'rb') as f:
|
||||
aes_key = f.read()
|
||||
aes_key = to_bytes(base64.b64decode(to_bytes(aes_key)))
|
||||
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
|
||||
else:
|
||||
logger.info('aes_key and aes_key_path is None, try to get key from default path')
|
||||
if os.path.exists(default_key_path):
|
||||
with open(default_key_path, 'rb') as f:
|
||||
aes_key = f.read()
|
||||
aes_key = to_bytes(base64.b64decode(to_bytes(aes_key)))
|
||||
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
|
||||
|
||||
if self.__ed_obj is None:
|
||||
logger.warning('fail to get aes key, will generate key')
|
||||
aes_key = random_key(_AES_256_KEY_SIZE)
|
||||
self.__ed_obj = AES.new(aes_key, AES.MODE_CTR, counter=self.__my_counter)
|
||||
if not os.path.exists(default_aes_dir):
|
||||
os.makedirs(default_aes_dir)
|
||||
|
||||
with open(default_key_path, 'wb') as f:
|
||||
aes_key = to_bytes(base64.b64encode(to_bytes(aes_key)))
|
||||
f.write(aes_key)
|
||||
|
||||
def init_data_cipher(self):
|
||||
"""初始化cipher"""
|
||||
encrypt_key = None
|
||||
encrypt_iv = None
|
||||
self.__data_key = self.get_data_key()
|
||||
self.__data_iv = self.data_cipher.get_counter_iv()
|
||||
start = iv_to_big_int(self.__data_iv)
|
||||
self.data_cipher.new_cipher(self.__data_key, start)
|
||||
|
||||
self.init_ed_obj()
|
||||
encrypt_key = self.__ed_obj.encrypt(self.__data_key)
|
||||
encrypt_iv = self.__ed_obj.encrypt(self.__data_iv)
|
||||
return encrypt_key, encrypt_iv
|
||||
|
||||
def init_data_cipter_by_user(self, encrypt_key, encrypt_iv, offset=0):
|
||||
"""根据密钥初始化cipher"""
|
||||
self.init_ed_obj()
|
||||
self.__data_key = self.__ed_obj.decrypt(encrypt_key)
|
||||
self.__data_iv = self.__ed_obj.decrypt(encrypt_iv)
|
||||
start = iv_to_big_int(self.__data_iv)
|
||||
self.data_cipher.new_cipher(self.__data_key, start, offset)
|
||||
|
||||
|
||||
class MetaHandle(object):
|
||||
"""用于获取/生成加密的元信息"""
|
||||
|
||||
def __init__(self, encrypt_key=None, encrypt_iv=None):
|
||||
"""初始化
|
||||
|
||||
:param encrypt_key(string): 加密的数据密钥
|
||||
:param encrypt_iv(bytes): 加密counter的初始值
|
||||
"""
|
||||
self.__encrypt_key = encrypt_key
|
||||
self.__encrypt_iv = encrypt_iv
|
||||
|
||||
def set_object_meta(self, headers):
|
||||
"""设置加密元信息到object的头部"""
|
||||
meta_data = dict()
|
||||
meta_data['x-cos-meta-client-side-encryption-key'] = to_bytes(base64.b64encode(to_bytes(self.__encrypt_key)))
|
||||
meta_data['x-cos-meta-client-side-encryption-iv'] = to_bytes(base64.b64encode(to_bytes(self.__encrypt_iv)))
|
||||
headers['Metadata'] = meta_data
|
||||
return headers
|
||||
|
||||
def get_object_meta(self, headers):
|
||||
"""从object的头部获取加密元信息"""
|
||||
if 'x-cos-meta-client-side-encryption-key' in headers and 'x-cos-meta-client-side-encryption-iv' in headers:
|
||||
self.__encrypt_key = base64.b64decode(to_bytes(headers['x-cos-meta-client-side-encryption-key']))
|
||||
self.__encrypt_iv = base64.b64decode(to_bytes(headers['x-cos-meta-client-side-encryption-iv']))
|
||||
return self.__encrypt_key, self.__encrypt_iv
|
||||
|
||||
|
||||
class DataEncryptAdapter(object):
|
||||
"""用于读取经过加密后的的数据"""
|
||||
|
||||
def __init__(self, data, content_len, data_cipher):
|
||||
self._data = to_bytes(data)
|
||||
self._data_cipher = data_cipher
|
||||
self._content_len = content_len
|
||||
self._read_len = 0
|
||||
|
||||
@property
|
||||
def len(self):
|
||||
return self._content_len
|
||||
|
||||
def read(self, length):
|
||||
"""读取加密后的数据"""
|
||||
if self._read_len >= self._content_len:
|
||||
return ''
|
||||
|
||||
if length is None or length < 0:
|
||||
bytes_to_read = self._content_len - self._read_len
|
||||
else:
|
||||
bytes_to_read = min(length, self._content_len - self._read_len)
|
||||
|
||||
if isinstance(self._data, bytes):
|
||||
content = self._data[self._read_len:self._read_len + bytes_to_read]
|
||||
else:
|
||||
content = self._data.read(bytes_to_read)
|
||||
|
||||
self._read_len += bytes_to_read
|
||||
content = self._data_cipher.encrypt(content)
|
||||
return content
|
||||
|
||||
|
||||
class DataDecryptAdapter(StreamBody):
|
||||
"""用于读取经过解密后的数据"""
|
||||
|
||||
def __init__(self, rt, data_cipher, offset=0):
|
||||
"""初始化
|
||||
:param rt(request object): request请求返回的对象
|
||||
:param data_cipher(an AES object): 数据加解密类
|
||||
:param offset(int): 数据读取的偏移量
|
||||
"""
|
||||
super(DataDecryptAdapter, self).__init__(rt)
|
||||
self._data_cipher = data_cipher
|
||||
self._offset = offset
|
||||
|
||||
def read(self, length, auto_decompress=False):
|
||||
"""读取解密后的数据"""
|
||||
if self._read_len >= self._content_len:
|
||||
return ''
|
||||
|
||||
if self._use_encoding and not auto_decompress:
|
||||
content = self._rt.raw.read(length)
|
||||
else:
|
||||
try:
|
||||
content = next(self._rt.iter_content(length))
|
||||
except StopIteration:
|
||||
return ''
|
||||
|
||||
content = self._data_cipher.decrypt(content)
|
||||
if self._read_len < self._offset and self._read_len + len(content) > self._offset:
|
||||
content = content[self._offset:]
|
||||
self._read_len = self._offset
|
||||
return content
|
||||
@@ -0,0 +1,176 @@
|
||||
# -*- coding=utf-8
|
||||
|
||||
from qcloud_cos import CosS3Auth
|
||||
from qcloud_cos.cos_client import logger, CosS3Client
|
||||
from .cos_comm import *
|
||||
|
||||
|
||||
class IntelligentSpeechClient(CosS3Client):
|
||||
|
||||
def _asr_hot_vocabulary_table(self, Bucket, Body=None, Params={}, Path="/asrhotvocabtable", Method="POST", **kwargs):
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = Params
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
params = format_values(params)
|
||||
|
||||
xml_config = None
|
||||
if Body is not None:
|
||||
xml_config = format_xml(data=Body, root='Request')
|
||||
path = Path
|
||||
url = self._conf.uri(bucket=Bucket, path=path, endpoint=self._conf._endpoint_ci)
|
||||
logger.info("_asr_hot_vocabulary_table result, url=:{url} ,headers=:{headers}, params=:{params}, xml_config=:{xml_config}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
xml_config=xml_config))
|
||||
rt = self.send_request(
|
||||
method=Method,
|
||||
url=url,
|
||||
bucket=Bucket,
|
||||
data=xml_config,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True,
|
||||
cos_request=False)
|
||||
|
||||
data = rt.content
|
||||
response = dict(**rt.headers)
|
||||
if 'Content-Type' in response and len(data) != 0:
|
||||
if response['Content-Type'] == 'application/xml':
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
elif response['Content-Type'].startswith('application/json'):
|
||||
data = rt.json()
|
||||
|
||||
return response, data
|
||||
|
||||
def ci_create_asr_hot_vocabulary_table(self, Bucket, Body, **kwargs):
|
||||
"""
|
||||
|
||||
:param Bucket(string): 存储桶名称
|
||||
:param Body(dict): 创建热词表body
|
||||
:param kwargs(dict): 设置请求的headers.
|
||||
:return(dict): 创建成功返回的结果, dict类型.
|
||||
|
||||
. code-block:: python
|
||||
|
||||
body = {
|
||||
"TableName": "test",
|
||||
"TableDescription": "test",
|
||||
"VocabularyWeights": {
|
||||
"Vocabulary": "abc",
|
||||
"Weight": "10"
|
||||
},
|
||||
# "VocabularyWeightStr": ""
|
||||
}
|
||||
response, data = client.ci_create_asr_hot_vocabulary_table(
|
||||
Bucket=bucket_name,
|
||||
Body=body,
|
||||
ContentType='application/xml'
|
||||
)
|
||||
print(response)
|
||||
print(data)
|
||||
return response, data
|
||||
"""
|
||||
return self._asr_hot_vocabulary_table(Bucket, Body, **kwargs)
|
||||
|
||||
def ci_update_asr_hot_vocabulary_table(self, Bucket, Body, **kwargs):
|
||||
"""
|
||||
|
||||
:param Bucket(string): 存储桶名称
|
||||
:param Body(dict): 更新热词表body
|
||||
:param kwargs(dict): 设置请求的headers.
|
||||
:return(dict): 更新成功返回的结果, dict类型.
|
||||
|
||||
. code-block:: python
|
||||
|
||||
body = {
|
||||
"TableId": "08417b95c91xxxxxxxxxxxxx",
|
||||
"TableName": "test1",
|
||||
"TableDescription": "test1",
|
||||
"VocabularyWeights": {
|
||||
"Vocabulary": "abc",
|
||||
"Weight": "8"
|
||||
},
|
||||
# "VocabularyWeightStr": ""
|
||||
}
|
||||
response, data = client.ci_update_asr_hot_vocabulary_table(
|
||||
Bucket=bucket_name,
|
||||
Body=body,
|
||||
ContentType='application/xml'
|
||||
)
|
||||
print(response)
|
||||
print(data)
|
||||
return response, data
|
||||
"""
|
||||
return self._asr_hot_vocabulary_table(Bucket=Bucket, Body=Body, Method="PUT", **kwargs)
|
||||
|
||||
def ci_get_asr_hot_vocabulary_table(self, Bucket, TableId, **kwargs):
|
||||
"""
|
||||
|
||||
:param Bucket(string): 存储桶名称
|
||||
:param TableId(string): 热词表ID
|
||||
:param kwargs(dict): 设置请求的headers.
|
||||
:return(dict): 查询成功返回的结果, dict类型.
|
||||
|
||||
. code-block:: python
|
||||
|
||||
response, data = client.ci_get_asr_hot_vocabulary_table(
|
||||
Bucket=bucket_name,
|
||||
TableId='08417b95c91xxxxxxxxxxxxx',
|
||||
)
|
||||
print(response)
|
||||
print(data)
|
||||
return response, data
|
||||
"""
|
||||
return self._asr_hot_vocabulary_table(Bucket=Bucket, Method="GET", Path="/asrhotvocabtable/" + TableId, **kwargs)
|
||||
|
||||
def ci_list_asr_hot_vocabulary_table(self, Bucket, Offset=0, Limit=10, **kwargs):
|
||||
"""
|
||||
|
||||
:param Bucket(string): 存储桶名称
|
||||
:param Offset(string): 热词表ID
|
||||
:param Limit(string): 热词表ID
|
||||
:param kwargs(dict): 设置请求的headers.
|
||||
:return(dict): 查询成功返回的结果, dict类型.
|
||||
|
||||
. code-block:: python
|
||||
|
||||
response, data = client.ci_list_asr_hot_vocabulary_table(
|
||||
Bucket=bucket_name,
|
||||
)
|
||||
print(response)
|
||||
print(data)
|
||||
return response, data
|
||||
"""
|
||||
param = {}
|
||||
param["offset"] = Offset
|
||||
param["limit"] = Limit
|
||||
return self._asr_hot_vocabulary_table(Bucket=Bucket, Method="GET", Params=param, **kwargs)
|
||||
|
||||
def ci_delete_asr_hot_vocabulary_table(self, Bucket, TableId, **kwargs):
|
||||
"""
|
||||
|
||||
:param Bucket(string): 存储桶名称
|
||||
:param TableId(string): 热词表ID
|
||||
:param kwargs(dict): 设置请求的headers.
|
||||
:return(dict): 查询成功返回的结果, dict类型.
|
||||
|
||||
. code-block:: python
|
||||
|
||||
response, data = client.ci_delete_asr_hot_vocabulary_table(
|
||||
Bucket=bucket_name,
|
||||
TableId='08417b95c91xxxxxxxxxxxxx',
|
||||
)
|
||||
print(response)
|
||||
print(data)
|
||||
return response, data
|
||||
"""
|
||||
return self._asr_hot_vocabulary_table(Bucket=Bucket, Method="DELETE", Path="/asrhotvocabtable/" + TableId, **kwargs)
|
||||
@@ -0,0 +1,908 @@
|
||||
# -*- coding=utf-8
|
||||
import json
|
||||
|
||||
from qcloud_cos import CosS3Auth
|
||||
from qcloud_cos.cos_client import logger, CosS3Client
|
||||
from .cos_comm import *
|
||||
|
||||
|
||||
class MetaInsightClient(CosS3Client):
|
||||
|
||||
def ci_create_dataset(self, Body, **kwargs):
|
||||
""" 创建数据集 https://cloud.tencent.com/document/product/460/106020
|
||||
|
||||
:param Body:(dict) 创建数据集配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 创建数据集
|
||||
response, data = client.ci_create_dataset(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "dataset"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_create_dataset result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_create_dataset_binding(self, Body, **kwargs):
|
||||
""" 绑定存储桶与数据集 https://cloud.tencent.com/document/product/460/106159
|
||||
|
||||
:param Body:(dict) 绑定存储桶与数据集配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 绑定存储桶与数据集
|
||||
response, data = client.ci_create_dataset_binding(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "datasetbinding"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_create_dataset_binding result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_create_file_meta_index(self, Body, **kwargs):
|
||||
""" 创建元数据索引 https://cloud.tencent.com/document/product/460/106022
|
||||
|
||||
:param Body:(dict) 创建元数据索引配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 创建元数据索引
|
||||
response, data = client.ci_create_file_meta_index(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "filemeta"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_create_file_meta_index result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_dataset_face_search(self, Body, **kwargs):
|
||||
""" 人脸搜索 https://cloud.tencent.com/document/product/460/106166
|
||||
|
||||
:param Body:(dict) 人脸搜索配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 人脸搜索
|
||||
response, data = client.ci_dataset_face_search(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "datasetquery" + "/" + "facesearch"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_dataset_face_search result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_dataset_simple_query(self, Body, **kwargs):
|
||||
""" 简单查询 https://cloud.tencent.com/document/product/460/106375
|
||||
|
||||
:param Body:(dict) 简单查询配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 简单查询
|
||||
response, data = client.ci_dataset_simple_query(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "datasetquery" + "/" + "simple"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_dataset_simple_query result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_delete_dataset(self, Body, **kwargs):
|
||||
""" 删除数据集 https://cloud.tencent.com/document/product/460/106157
|
||||
|
||||
:param Body:(dict) 删除数据集配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 删除数据集
|
||||
response, data = client.ci_delete_dataset(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "dataset"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_delete_dataset result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='DELETE',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_delete_dataset_binding(self, Body, **kwargs):
|
||||
""" 解绑存储桶与数据集 https://cloud.tencent.com/document/product/460/106160
|
||||
|
||||
:param Body:(dict) 解绑存储桶与数据集配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 解绑存储桶与数据集
|
||||
response, data = client.ci_delete_dataset_binding(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "datasetbinding"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_delete_dataset_binding result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='DELETE',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_delete_file_meta_index(self, Body, **kwargs):
|
||||
""" 删除元数据索引 https://cloud.tencent.com/document/product/460/106163
|
||||
|
||||
:param Body:(dict) 删除元数据索引配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 删除元数据索引
|
||||
response, data = client.ci_delete_file_meta_index(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "filemeta"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_delete_file_meta_index result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='DELETE',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_describe_dataset(self, DatasetName, Statistics=False, **kwargs):
|
||||
""" 查询数据集 https://cloud.tencent.com/document/product/460/106155
|
||||
|
||||
:param DatasetName:(string) 数据集名称,同一个账户下唯一。.
|
||||
:param Statistics:(bool) 是否需要实时统计数据集中文件相关信息。有效值: false:不统计,返回的文件的总大小、数量信息可能不正确也可能都为0。 true:需要统计,返回数据集中当前的文件的总大小、数量信息。 默认值为false。.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 查询数据集
|
||||
response, data = client.ci_describe_dataset(
|
||||
Datasetname='',
|
||||
Statistics=''
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
params["datasetname"] = DatasetName
|
||||
params["statistics"] = Statistics
|
||||
|
||||
params = format_values(params)
|
||||
|
||||
path = "/" + "dataset"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_describe_dataset result, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_describe_dataset_binding(self, DatasetName, Uri, **kwargs):
|
||||
""" 查询数据集与存储桶的绑定关系 https://cloud.tencent.com/document/product/460/106485
|
||||
|
||||
:param DatasetName:(string) 数据集名称,同一个账户下唯一。.
|
||||
:param Uri:(string) 资源标识字段,表示需要与数据集绑定的资源,当前仅支持COS存储桶,字段规则:cos://,其中BucketName表示COS存储桶名称,例如(需要进行urlencode):cos%3A%2F%2Fexample-125000.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 查询数据集与存储桶的绑定关系
|
||||
response, data = client.ci_describe_dataset_binding(
|
||||
DatasetName='',
|
||||
Uri=''
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
params["datasetname"] = DatasetName
|
||||
params["uri"] = Uri
|
||||
|
||||
params = format_values(params)
|
||||
|
||||
path = "/" + "datasetbinding"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_describe_dataset_binding result, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_describe_dataset_bindings(self, DatasetName, NextToken=None, MaxResults=100, **kwargs):
|
||||
""" 查询绑定关系列表 https://cloud.tencent.com/document/product/460/106161
|
||||
|
||||
:param DatasetName:(string) 数据集名称,同一个账户下唯一。.
|
||||
:param MaxResults:(int) 返回绑定关系的最大个数,取值范围为0~200。不设置此参数或者设置为0时,则默认值为100。.
|
||||
:param NextToken:(string) 当绑定关系总数大于设置的MaxResults时,用于翻页的token。从NextToken开始按字典序返回绑定关系信息列表。第一次调用此接口时,设置为空。.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 查询绑定关系列表
|
||||
response, data = client.ci_describe_dataset_bindings(
|
||||
DatasetName='',
|
||||
MaxResults='',
|
||||
NextToken=''
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
params["datasetname"] = DatasetName
|
||||
if NextToken is not None:
|
||||
params["nexttoken"] = NextToken
|
||||
params["maxresults"] = MaxResults
|
||||
|
||||
params = format_values(params)
|
||||
|
||||
path = "/" + "datasetbindings"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_describe_dataset_bindings result, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_describe_datasets(self, NextToken=None, Prefix=None, MaxResults=100, **kwargs):
|
||||
""" 列出数据集 https://cloud.tencent.com/document/product/460/106158
|
||||
|
||||
:param MaxResults:(int) 本次返回数据集的最大个数,取值范围为0~200。不设置此参数或者设置为0时,则默认值为100。.
|
||||
:param NextToken:(string) 翻页标记。当文件总数大于设置的MaxResults时,用于翻页的Token。从NextToken开始按字典序返回文件信息列表。填写上次查询返回的值,首次使用时填写为空。.
|
||||
:param Prefix:(string) 数据集名称前缀。.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 列出数据集
|
||||
response, data = client.ci_describe_datasets(
|
||||
MaxResults='',
|
||||
NextToken='',
|
||||
Prefix=''
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
if NextToken is not None:
|
||||
params["nexttoken"] = NextToken
|
||||
if Prefix is not None:
|
||||
params["prefix"] = Prefix
|
||||
params["maxresults"] = MaxResults
|
||||
|
||||
params = format_values(params)
|
||||
|
||||
path = "/" + "datasets"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_describe_datasets result, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_describe_file_meta_index(self, DatasetName, Uri, **kwargs):
|
||||
""" 查询元数据索引 https://cloud.tencent.com/document/product/460/106164
|
||||
|
||||
:param DatasetName:(string) 数据集名称,同一个账户下唯一。.
|
||||
:param Uri:(string) 资源标识字段,表示需要建立索引的文件地址,当前仅支持 COS 上的文件,字段规则:cos://<BucketName>/<ObjectKey>,其中BucketName表示 COS 存储桶名称,ObjectKey 表示文件完整路径,例如:cos://examplebucket-1250000000/test1/img.jpg。 注意: 仅支持本账号内的 COS 文件 不支持 HTTP 开头的地址 需 UrlEncode.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 查询元数据索引
|
||||
response, data = client.ci_describe_file_meta_index(
|
||||
Datasetname='',
|
||||
Uri=''
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
params["datasetname"] = DatasetName
|
||||
params["uri"] = Uri
|
||||
|
||||
params = format_values(params)
|
||||
|
||||
path = "/" + "filemeta"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_describe_file_meta_index result, url=:{url} ,headers=:{headers}, params=:{params}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params))
|
||||
rt = self.send_request(
|
||||
method='GET',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_search_image(self, Body, **kwargs):
|
||||
""" 图像检索 https://cloud.tencent.com/document/product/460/106376
|
||||
|
||||
:param Body:(dict) 图像检索配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 图像检索
|
||||
response, data = client.ci_search_image(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "datasetquery" + "/" + "imagesearch"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_search_image result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='POST',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_update_dataset(self, Body, **kwargs):
|
||||
""" 更新数据集 https://cloud.tencent.com/document/product/460/106156
|
||||
|
||||
:param Body:(dict) 更新数据集配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 更新数据集
|
||||
response, data = client.ci_update_dataset(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "dataset"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_update_dataset result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='PUT',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
|
||||
def ci_update_file_meta_index(self, Body, **kwargs):
|
||||
""" 更新元数据索引 https://cloud.tencent.com/document/product/460/106162
|
||||
|
||||
:param Body:(dict) 更新元数据索引配置信息.
|
||||
:param kwargs:(dict) 设置上传的headers.
|
||||
:return(dict): response header.
|
||||
:return(dict): 请求成功返回的结果,dict类型.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token) # 获取配置对象
|
||||
client = CosS3Client(config)
|
||||
# 更新元数据索引
|
||||
response, data = client.ci_update_file_meta_index(
|
||||
Body={}
|
||||
)
|
||||
print data
|
||||
print response
|
||||
"""
|
||||
headers = mapped(kwargs)
|
||||
final_headers = {}
|
||||
params = {}
|
||||
for key in headers:
|
||||
if key.startswith("response"):
|
||||
params[key] = headers[key]
|
||||
else:
|
||||
final_headers[key] = headers[key]
|
||||
headers = final_headers
|
||||
|
||||
params = format_values(params)
|
||||
body = json.dumps(Body)
|
||||
path = "/" + "filemeta"
|
||||
url = self._conf.uri(path=path, endpoint=self._conf._endpoint_ci, useAppid=True)
|
||||
|
||||
logger.info("ci_update_file_meta_index result, url=:{url} ,headers=:{headers}, params=:{params},body=:{body}".format(
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
body=body))
|
||||
rt = self.send_request(
|
||||
method='PUT',
|
||||
url=url,
|
||||
appid=self._conf._appid,
|
||||
data=body,
|
||||
auth=CosS3Auth(self._conf, path, params=params),
|
||||
params=params,
|
||||
headers=headers,
|
||||
ci_request=True)
|
||||
|
||||
data = xml_to_dict(rt.content)
|
||||
format_dict(data, ['Response'])
|
||||
|
||||
response = dict(**rt.headers)
|
||||
return response, data
|
||||
@@ -0,0 +1,234 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import errno
|
||||
import threading
|
||||
import logging
|
||||
import uuid
|
||||
import hashlib
|
||||
import crcmod
|
||||
from .cos_comm import *
|
||||
from .streambody import StreamBody
|
||||
from .cos_threadpool import SimpleThreadPool
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResumableDownLoader(object):
|
||||
def __init__(self, cos_client, bucket, key, dest_filename, object_info, part_size=20, max_thread=5,
|
||||
max_part_count=100, enable_crc=False, progress_callback=None, dump_record_dir=None, key_simplify_check=True, **kwargs):
|
||||
self.__cos_client = cos_client
|
||||
self.__bucket = bucket
|
||||
self.__key = key
|
||||
self.__dest_file_path = os.path.abspath(dest_filename)
|
||||
self.__object_info = object_info
|
||||
self.__max_thread = max_thread
|
||||
self.__enable_crc = enable_crc
|
||||
self.__progress_callback = progress_callback
|
||||
self.__headers = kwargs
|
||||
self.__key_simplify_check = key_simplify_check
|
||||
|
||||
self.__max_part_count = max_part_count # 取决于服务端是否对并发有限制
|
||||
self.__min_part_size = 1024 * 1024 # 1M
|
||||
self.__part_size = self.__determine_part_size_internal(int(object_info['Content-Length']), part_size)
|
||||
self.__finished_parts = []
|
||||
self.__lock = threading.Lock()
|
||||
self.__record = None # 记录当前的上下文
|
||||
if not dump_record_dir:
|
||||
self.__dump_record_dir = os.path.join(os.path.expanduser('~'), '.cos_download_tmp_file')
|
||||
else:
|
||||
self.__dump_record_dir = dump_record_dir
|
||||
|
||||
record_filename = self.__get_record_filename(bucket, key, self.__dest_file_path)
|
||||
self.__record_filepath = os.path.join(self.__dump_record_dir, record_filename)
|
||||
self.__tmp_file = None
|
||||
|
||||
if not os.path.exists(self.__dump_record_dir):
|
||||
# 多进程并发情况下makedirs会出现冲突, 需要进行异常捕获
|
||||
try:
|
||||
os.makedirs(self.__dump_record_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
logger.error('os makedir error: dir: {0}, errno {1}'.format(self.__dump_record_dir, e.errno))
|
||||
raise
|
||||
pass
|
||||
logger.debug('resumale downloader init finish, bucket: {0}, key: {1}'.format(bucket, key))
|
||||
|
||||
def start(self):
|
||||
logger.debug('start resumable download, bucket: {0}, key: {1}'.format(self.__bucket, self.__key))
|
||||
self.__load_record() # 从record文件中恢复读取上下文
|
||||
|
||||
assert self.__tmp_file
|
||||
open(self.__tmp_file, 'a').close()
|
||||
|
||||
# 已完成分块先设置下载进度
|
||||
if self.__progress_callback:
|
||||
for finished_part in self.__finished_parts:
|
||||
self.__progress_callback.report(finished_part.length)
|
||||
|
||||
parts_need_to_download = self.__get_parts_need_to_download()
|
||||
logger.debug('parts_need_to_download: {0}'.format(parts_need_to_download))
|
||||
pool = SimpleThreadPool(self.__max_thread)
|
||||
for part in parts_need_to_download:
|
||||
part_range = "bytes=" + str(part.start) + "-" + str(part.start + part.length - 1)
|
||||
headers = dict.copy(self.__headers)
|
||||
headers["Range"] = part_range
|
||||
pool.add_task(self.__download_part, part, headers)
|
||||
|
||||
pool.wait_completion()
|
||||
result = pool.get_result()
|
||||
if not result['success_all']:
|
||||
raise CosClientError('some download_part fail after max_retry, please download_file again')
|
||||
|
||||
if os.path.exists(self.__dest_file_path):
|
||||
os.remove(self.__dest_file_path)
|
||||
os.rename(self.__tmp_file, self.__dest_file_path)
|
||||
|
||||
if self.__enable_crc:
|
||||
self.__check_crc()
|
||||
|
||||
self.__del_record()
|
||||
logger.debug('download success, bucket: {0}, key: {1}'.format(self.__bucket, self.__key))
|
||||
|
||||
def __get_record_filename(self, bucket, key, dest_file_path):
|
||||
dest_file_path_md5 = hashlib.md5(dest_file_path.encode("utf-8")).hexdigest()
|
||||
key_md5 = hashlib.md5(key.encode("utf-8")).hexdigest()
|
||||
return '{0}_{1}.{2}'.format(bucket, key_md5, dest_file_path_md5)
|
||||
|
||||
def __determine_part_size_internal(self, file_size, part_size):
|
||||
real_part_size = part_size * 1024 * 1024 # MB
|
||||
if real_part_size < self.__min_part_size:
|
||||
real_part_size = self.__min_part_size
|
||||
|
||||
while real_part_size * self.__max_part_count < file_size:
|
||||
real_part_size = real_part_size * 2
|
||||
logger.debug('finish to determine part size, file_size: {0}, part_size: {1}'.format(file_size, real_part_size))
|
||||
return real_part_size
|
||||
|
||||
def __splite_to_parts(self):
|
||||
parts = []
|
||||
file_size = int(self.__object_info['Content-Length'])
|
||||
num_parts = int((file_size + self.__part_size - 1) / self.__part_size)
|
||||
for i in range(num_parts):
|
||||
start = i * self.__part_size
|
||||
if i == num_parts - 1:
|
||||
length = file_size - start
|
||||
else:
|
||||
length = self.__part_size
|
||||
|
||||
parts.append(PartInfo(i + 1, start, length))
|
||||
return parts
|
||||
|
||||
def __get_parts_need_to_download(self):
|
||||
all_set = set(self.__splite_to_parts())
|
||||
logger.debug('all_set: {0}'.format(len(all_set)))
|
||||
finished_set = set(self.__finished_parts)
|
||||
logger.debug('finished_set: {0}'.format(len(finished_set)))
|
||||
return list(all_set - finished_set)
|
||||
|
||||
def __download_part(self, part, headers):
|
||||
with open(self.__tmp_file, 'rb+') as f:
|
||||
f.seek(part.start, 0)
|
||||
range = None
|
||||
traffic_limit = None
|
||||
if 'Range' in headers:
|
||||
range = headers['Range']
|
||||
|
||||
if 'TrafficLimit' in headers:
|
||||
traffic_limit = headers['TrafficLimit']
|
||||
logger.debug("part_id: {0}, part_range: {1}, traffic_limit:{2}".format(part.part_id, range, traffic_limit))
|
||||
result = self.__cos_client.get_object(Bucket=self.__bucket, Key=self.__key, KeySimplifyCheck=self.__key_simplify_check, **headers)
|
||||
result["Body"].pget_stream_to_file(f, part.start, part.length)
|
||||
|
||||
self.__finish_part(part)
|
||||
|
||||
if self.__progress_callback:
|
||||
self.__progress_callback.report(part.length)
|
||||
|
||||
def __finish_part(self, part):
|
||||
logger.debug('download part finished,bucket: {0}, key: {1}, part_id: {2}'.
|
||||
format(self.__bucket, self.__key, part.part_id))
|
||||
with self.__lock:
|
||||
self.__finished_parts.append(part)
|
||||
self.__record['parts'].append({'part_id': part.part_id, 'start': part.start, 'length': part.length})
|
||||
self.__dump_record(self.__record)
|
||||
|
||||
def __dump_record(self, record):
|
||||
record_filepath = self.__record_filepath
|
||||
if os.path.exists(self.__record_filepath):
|
||||
record_filepath += '.tmp'
|
||||
with open(record_filepath, 'w') as f:
|
||||
json.dump(record, f)
|
||||
logger.debug(
|
||||
'dump record to {0}, bucket: {1}, key: {2}'.format(record_filepath, self.__bucket, self.__key))
|
||||
if record_filepath != self.__record_filepath:
|
||||
os.remove(self.__record_filepath)
|
||||
os.rename(record_filepath, self.__record_filepath)
|
||||
|
||||
def __load_record(self):
|
||||
record = None
|
||||
|
||||
if os.path.exists(self.__record_filepath):
|
||||
with open(self.__record_filepath, 'r') as f:
|
||||
record = json.load(f)
|
||||
ret = self.__check_record(record)
|
||||
# record记录是否跟head object的一致,不一致则删除
|
||||
if not ret:
|
||||
self.__del_record()
|
||||
record = None
|
||||
else:
|
||||
self.__part_size = record['part_size']
|
||||
self.__tmp_file = record['tmp_filename']
|
||||
if not os.path.exists(self.__tmp_file):
|
||||
record = None
|
||||
self.__tmp_file = None
|
||||
self.__del_record()
|
||||
else:
|
||||
self.__finished_parts = list(
|
||||
PartInfo(p['part_id'], p['start'], p['length']) for p in record['parts'])
|
||||
logger.debug('load record: finished parts nums: {0}'.format(len(self.__finished_parts)))
|
||||
self.__record = record
|
||||
|
||||
if not record:
|
||||
self.__tmp_file = "{file_name}_{uuid}".format(file_name=self.__dest_file_path, uuid=uuid.uuid4().hex)
|
||||
record = {'bucket': self.__bucket, 'key': self.__key, 'tmp_filename': self.__tmp_file,
|
||||
'mtime': self.__object_info['Last-Modified'], 'etag': self.__object_info['ETag'],
|
||||
'file_size': self.__object_info['Content-Length'], 'part_size': self.__part_size, 'parts': []}
|
||||
self.__record = record
|
||||
self.__dump_record(record)
|
||||
|
||||
def __check_record(self, record):
|
||||
return record['etag'] == self.__object_info['ETag'] and \
|
||||
record['mtime'] == self.__object_info['Last-Modified'] and \
|
||||
record['file_size'] == self.__object_info['Content-Length']
|
||||
|
||||
def __del_record(self):
|
||||
os.remove(self.__record_filepath)
|
||||
logger.debug('ResumableDownLoader delete record_file, path: {0}'.format(self.__record_filepath))
|
||||
|
||||
def __check_crc(self):
|
||||
logger.debug('start to check crc')
|
||||
c64 = crcmod.mkCrcFun(0x142F0E1EBA9EA3693, initCrc=0, xorOut=0xffffffffffffffff, rev=True)
|
||||
with open(self.__dest_file_path, 'rb') as f:
|
||||
local_crc64 = str(c64(f.read()))
|
||||
object_crc64 = self.__object_info['x-cos-hash-crc64ecma']
|
||||
if local_crc64 is not None and object_crc64 is not None and local_crc64 != object_crc64:
|
||||
raise CosClientError('crc of client: {0} is mismatch with cos: {1}'.format(local_crc64, object_crc64))
|
||||
|
||||
|
||||
class PartInfo(object):
|
||||
def __init__(self, part_id, start, length):
|
||||
self.part_id = part_id
|
||||
self.start = start
|
||||
self.length = length
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__key() == other.__key()
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__key())
|
||||
|
||||
def __key(self):
|
||||
return self.part_id, self.start, self.length
|
||||
@@ -0,0 +1,97 @@
|
||||
# -*- coding=utf-8
|
||||
import os
|
||||
import uuid
|
||||
import struct
|
||||
import logging
|
||||
from .cos_comm import xml_to_dict
|
||||
from .cos_comm import to_unicode
|
||||
from .cos_exception import CosServiceError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EventStream():
|
||||
def __init__(self, rt):
|
||||
self._rt = rt
|
||||
self._raw = self._rt.raw
|
||||
self._finish = False
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
return self.next_event()
|
||||
|
||||
next = __next__
|
||||
|
||||
def next_event(self):
|
||||
"""获取下一个事件"""
|
||||
if self._finish:
|
||||
"""要把剩下的内容读完丢弃或者自己关连接,否则不会自动关连接"""
|
||||
self._raw.read()
|
||||
raise StopIteration
|
||||
total_byte_length = struct.unpack('>I', bytes(self._raw.read(4)))[0] # message总长度
|
||||
header_byte_length = struct.unpack('>I', bytes(self._raw.read(4)))[0] # header总长度
|
||||
prelude_crc = struct.unpack('>I', bytes(self._raw.read(4)))[0]
|
||||
# 处理headers
|
||||
offset = 0
|
||||
msg_headers = {}
|
||||
while offset < header_byte_length:
|
||||
header_name_length = struct.unpack('>B', bytes(self._raw.read(1)))[0]
|
||||
header_name = to_unicode(self._raw.read(header_name_length))
|
||||
header_value_type = struct.unpack('>B', bytes(self._raw.read(1)))[0]
|
||||
header_value_length = struct.unpack('>H', bytes(self._raw.read(2)))[0]
|
||||
header_value = to_unicode(self._raw.read(header_value_length))
|
||||
msg_headers[header_name] = header_value
|
||||
offset += 4 + header_name_length + header_value_length
|
||||
# 处理payload(输出给用户的dict中也为bytes)
|
||||
payload_byte_length = total_byte_length - header_byte_length - 16 # payload总长度
|
||||
payload = self._raw.read(payload_byte_length)
|
||||
message_crc = struct.unpack('>I', bytes(self._raw.read(4)))[0]
|
||||
if ':message-type' in msg_headers and msg_headers[':message-type'] == 'event':
|
||||
if ':event-type' in msg_headers and msg_headers[':event-type'] == "Records":
|
||||
return {'Records': {'Payload': payload}}
|
||||
elif ':event-type' in msg_headers and msg_headers[':event-type'] == "Stats":
|
||||
return {'Stats': {'Details': xml_to_dict(payload)}}
|
||||
elif ':event-type' in msg_headers and msg_headers[':event-type'] == "Progress":
|
||||
return {'Progress': {'Details': xml_to_dict(payload)}}
|
||||
elif ':event-type' in msg_headers and msg_headers[':event-type'] == "Cont":
|
||||
return {'Cont': {}}
|
||||
elif ':event-type' in msg_headers and msg_headers[':event-type'] == "End":
|
||||
self._finish = True
|
||||
return {'End': {}}
|
||||
# 处理Error Message(抛出异常)
|
||||
if ':message-type' in msg_headers and msg_headers[':message-type'] == 'error':
|
||||
error_info = dict()
|
||||
error_info['code'] = msg_headers[':error-code']
|
||||
error_info['message'] = msg_headers[':error-message']
|
||||
error_info['resource'] = self._rt.request.url
|
||||
error_info['requestid'] = ''
|
||||
error_info['traceid'] = ''
|
||||
if 'x-cos-request-id' in self._rt.headers:
|
||||
error_info['requestid'] = self._rt.headers['x-cos-request-id']
|
||||
if 'x-cos-trace-id' in self._rt.headers:
|
||||
error_info['traceid'] = self._rt.headers['x-cos-trace-id']
|
||||
logger.error(error_info)
|
||||
e = CosServiceError('POST', error_info, self._rt.status_code)
|
||||
raise e
|
||||
|
||||
def get_select_result(self):
|
||||
"""获取查询结果"""
|
||||
data = b""
|
||||
for event in self:
|
||||
if 'Records' in event:
|
||||
data += event['Records']['Payload']
|
||||
return data
|
||||
|
||||
def get_select_result_to_file(self, file_name):
|
||||
"""保存查询结果到文件"""
|
||||
tmp_file_name = "{file_name}_{uuid}".format(file_name=file_name, uuid=uuid.uuid4().hex)
|
||||
with open(tmp_file_name, 'wb') as fp:
|
||||
for event in self:
|
||||
if 'Records' in event:
|
||||
data = event['Records']['Payload']
|
||||
fp.write(data)
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
os.rename(tmp_file_name, file_name)
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- coding=utf-8
|
||||
import os
|
||||
import uuid
|
||||
|
||||
|
||||
class StreamBody(object):
|
||||
def __init__(self, rt):
|
||||
self._rt = rt
|
||||
self._read_len = 0
|
||||
self._content_len = 0
|
||||
self._use_chunked = False
|
||||
self._use_encoding = False
|
||||
if 'Content-Length' in self._rt.headers:
|
||||
self._content_len = int(self._rt.headers['Content-Length'])
|
||||
elif 'Transfer-Encoding' in self._rt.headers and self._rt.headers['Transfer-Encoding'] == "chunked":
|
||||
self._use_chunked = True
|
||||
else:
|
||||
raise IOError("create StreamBody failed without Content-Length header or Transfer-Encoding header")
|
||||
|
||||
if 'Content-Encoding' in self._rt.headers:
|
||||
self._use_encoding = True
|
||||
|
||||
def __iter__(self):
|
||||
"""提供一个默认的迭代器"""
|
||||
return self._rt.iter_content(1024)
|
||||
|
||||
def __len__(self):
|
||||
return self._content_len
|
||||
|
||||
def get_raw_stream(self):
|
||||
"""提供原始流"""
|
||||
return self._rt.raw
|
||||
|
||||
def get_stream(self, chunk_size=1024):
|
||||
"""提供一个chunk可变的迭代器"""
|
||||
return self._rt.iter_content(chunk_size=chunk_size)
|
||||
|
||||
def read(self, chunk_size=1024, auto_decompress=False):
|
||||
chunk = None
|
||||
if self._use_encoding and not auto_decompress:
|
||||
chunk = self._rt.raw.read(chunk_size)
|
||||
else:
|
||||
try:
|
||||
chunk = next(self._rt.iter_content(chunk_size))
|
||||
except StopIteration:
|
||||
return ''
|
||||
return chunk
|
||||
|
||||
def get_stream_to_file(self, file_name, disable_tmp_file=False, auto_decompress=False):
|
||||
"""保存流到本地文件"""
|
||||
self._read_len = 0
|
||||
tmp_file_name = "{file_name}_{uuid}".format(file_name=file_name, uuid=uuid.uuid4().hex)
|
||||
if disable_tmp_file:
|
||||
tmp_file_name = file_name
|
||||
chunk_size = 1024 * 1024
|
||||
with open(tmp_file_name, 'wb') as fp:
|
||||
while True:
|
||||
chunk = self.read(chunk_size, auto_decompress)
|
||||
if not chunk:
|
||||
break
|
||||
self._read_len += len(chunk)
|
||||
fp.write(chunk)
|
||||
|
||||
if not self._use_chunked and not (
|
||||
self._use_encoding and auto_decompress) and self._read_len != self._content_len:
|
||||
if os.path.exists(tmp_file_name):
|
||||
os.remove(tmp_file_name)
|
||||
raise IOError("download failed with incomplete file")
|
||||
if file_name != tmp_file_name:
|
||||
if os.path.exists(file_name):
|
||||
os.remove(file_name)
|
||||
os.rename(tmp_file_name, file_name)
|
||||
|
||||
def pget_stream_to_file(self, fdst, offset, expected_len, auto_decompress=False):
|
||||
"""保存流到本地文件的offset偏移"""
|
||||
self._read_len = 0
|
||||
fdst.seek(offset, 0)
|
||||
chunk_size = 1024 * 1024
|
||||
while True:
|
||||
chunk = self.read(chunk_size, auto_decompress)
|
||||
if not chunk:
|
||||
break
|
||||
self._read_len += len(chunk)
|
||||
fdst.write(chunk)
|
||||
|
||||
if not self._use_chunked and not (self._use_encoding and auto_decompress) and self._read_len != expected_len:
|
||||
raise IOError("download failed with incomplete file")
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '5.1.9.40'
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- coding=utf-8
|
||||
import xml.etree.ElementTree
|
||||
|
||||
|
||||
class Xml2Dict(dict):
|
||||
|
||||
def __init__(self, parent_node):
|
||||
if parent_node.items():
|
||||
self.updateDict(dict(parent_node.items()))
|
||||
if len(parent_node) == 0:
|
||||
self.updateDict({parent_node.tag: parent_node.text})
|
||||
for element in parent_node:
|
||||
if len(element):
|
||||
aDict = Xml2Dict(element)
|
||||
self.updateDict({element.tag: aDict})
|
||||
elif element.items():
|
||||
elementattrib = element.items()
|
||||
if element.text:
|
||||
elementattrib.append((element.tag, element.text))
|
||||
self.updateDict({element.tag: dict(elementattrib)})
|
||||
else:
|
||||
self.updateDict({element.tag: element.text})
|
||||
|
||||
def updateDict(self, aDict):
|
||||
for key in aDict:
|
||||
if key in self:
|
||||
value = self.pop(key)
|
||||
if type(value) is not list:
|
||||
lst = list()
|
||||
lst.append(value)
|
||||
lst.append(aDict[key])
|
||||
self.update({key: lst})
|
||||
else:
|
||||
value.append(aDict[key])
|
||||
self.update({key: value})
|
||||
else:
|
||||
self.update({key: aDict[key]})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
# s = """<?xml version="1.0" encoding="utf-8" ?>
|
||||
# <result xmlns= "wqa.bai.com">
|
||||
# <count n="1">10</count>
|
||||
# <data><id>1</id><name>test1</name></data>
|
||||
# <data><id>2</id><name>test2</name></data>
|
||||
# <data><id>3</id><name>test3</name></data>
|
||||
# </result>"""
|
||||
# root = xml.etree.ElementTree.fromstring(s)
|
||||
# xmldict = Xml2Dict(root)
|
||||
# print(xmldict)
|
||||
Reference in New Issue
Block a user