第一次提交:阿龙电竞 Django 后端最新版本

This commit is contained in:
XingQue
2026-06-14 02:23:24 +08:00
commit 4f7bf00224
9654 changed files with 5417000 additions and 0 deletions

View File

@@ -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())

File diff suppressed because it is too large Load Diff

View 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

File diff suppressed because it is too large Load Diff

View 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)

View File

@@ -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

View File

@@ -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"

View File

@@ -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!',)])]

View 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

View File

@@ -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)

View File

@@ -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存储桶名称例如需要进行urlencodecos%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

View File

@@ -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

View File

@@ -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)

View File

@@ -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")

View File

@@ -0,0 +1 @@
__version__ = '5.1.9.40'

View File

@@ -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)