Skip to content

Commit 94dfbcf

Browse files
authored
Merge pull request #38 from dt3310321/s3
S3
2 parents 1fa7e6a + 4230470 commit 94dfbcf

File tree

7 files changed

+897
-76
lines changed

7 files changed

+897
-76
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ install:
1010
- pip install dicttoxml
1111
script:
1212
- pep8 --max-line-length=180 qcloud_cos/.
13-
- nosetests -s -v
13+
- nosetests -s -v ut/
1414
deploy:
1515
provider: pypi
1616
distributions: sdist bdist_wheel

qcloud_cos/cos_client.py

+761-62
Large diffs are not rendered by default.

qcloud_cos/cos_comm.py

+56-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import xml.dom.minidom
99
import xml.etree.ElementTree
1010
from urllib import quote
11+
from urllib import unquote
1112
from xml2dict import Xml2Dict
1213
from dicttoxml import dicttoxml
1314
from cos_exception import CosClientError
1415
from cos_exception import CosServiceError
1516

1617
SINGLE_UPLOAD_LENGTH = 5*1024*1024*1024 # 单次上传文件最大为5G
18+
LOGGING_UIN = 'id="qcs::cam::uin/100001001014:uin/100001001014"'
1719
# kwargs中params到http headers的映射
1820
maplist = {
1921
'ContentLength': 'Content-Length',
@@ -45,7 +47,12 @@
4547
'CopySourceIfNoneMatch': 'x-cos-copy-source-If-None-Match',
4648
'CopySourceIfModifiedSince': 'x-cos-copy-source-If-Modified-Since',
4749
'CopySourceIfUnmodifiedSince': 'x-cos-copy-source-If-Unmodified-Since',
48-
'VersionId': 'x-cos-version-id',
50+
'VersionId': 'versionId',
51+
'ServerSideEncryption': 'x-cos-server-side-encryption',
52+
'SSECustomerAlgorithm': 'x-cos-server-side-encryption-customer-algorithm',
53+
'SSECustomerKey': 'x-cos-server-side-encryption-customer-key',
54+
'SSECustomerKeyMD5': 'x-cos-server-side-encryption-customer-key-MD5',
55+
'SSEKMSKeyId': 'x-cos-server-side-encryption-cos-kms-key-id'
4956
}
5057

5158

@@ -62,6 +69,21 @@ def get_md5(data):
6269
return MD5
6370

6471

72+
def get_content_md5(body):
73+
body_type = type(body)
74+
if body_type == str:
75+
return get_md5(body)
76+
elif body_type == file:
77+
if hasattr(body, 'tell') and hasattr(body, 'seek') and hasattr(body, 'read'):
78+
file_position = body.tell() # 记录文件当前位置
79+
md5_str = get_md5(body.read())
80+
body.seek(file_position) # 恢复初始的文件位置
81+
return md5_str
82+
else:
83+
raise CosClientError('can not get md5 digest for file without necessary attrs, including tell, seek and read')
84+
return None
85+
86+
6587
def dict_to_xml(data):
6688
"""V5使用xml格式,将输入的dict转换为xml"""
6789
doc = xml.dom.minidom.Document()
@@ -98,6 +120,8 @@ def xml_to_dict(data, origin_str="", replace_str=""):
98120
xmldict = Xml2Dict(root)
99121
xmlstr = str(xmldict)
100122
xmlstr = xmlstr.replace("{http://www.qcloud.com/document/product/436/7751}", "")
123+
xmlstr = xmlstr.replace("{https://cloud.tencent.com/document/product/436}", "")
124+
xmlstr = xmlstr.replace("{http://doc.s3.amazonaws.com/2006-03-01}", "")
101125
xmlstr = xmlstr.replace("{http://www.w3.org/2001/XMLSchema-instance}", "")
102126
if origin_str:
103127
xmlstr = xmlstr.replace(origin_str, replace_str)
@@ -193,6 +217,7 @@ def format_path(path):
193217
def get_copy_source_info(CopySource):
194218
"""获取拷贝源的所有信息"""
195219
appid = ""
220+
versionid = ""
196221
if 'Appid' in CopySource.keys():
197222
appid = CopySource['Appid']
198223
if 'Bucket' in CopySource.keys():
@@ -209,13 +234,17 @@ def get_copy_source_info(CopySource):
209234
path = CopySource['Key']
210235
else:
211236
raise CosClientError('CopySource Need Parameter Key')
212-
return bucket, path, region
237+
if 'VersionId' in CopySource.keys():
238+
versionid = CopySource['VersionId']
239+
return bucket, path, region, versionid
213240

214241

215242
def gen_copy_source_url(CopySource):
216243
"""拼接拷贝源url"""
217-
bucket, path, region = get_copy_source_info(CopySource)
244+
bucket, path, region, versionid = get_copy_source_info(CopySource)
218245
path = format_path(path)
246+
if versionid != '':
247+
path = path + '?versionId=' + versionid
219248
url = "{bucket}.{region}.myqcloud.com/{path}".format(
220249
bucket=bucket,
221250
region=region,
@@ -245,3 +274,27 @@ def deal_with_empty_file_stream(data):
245274
except io.UnsupportedOperation:
246275
return ""
247276
return data
277+
278+
279+
def format_dict(data, key_lst):
280+
"""转换返回dict中的可重复字段为list"""
281+
for key in key_lst:
282+
# 将dict转为list,保持一致
283+
if key in data.keys() and isinstance(data[key], dict):
284+
lst = []
285+
lst.append(data[key])
286+
data[key] = lst
287+
return data
288+
289+
290+
def decode_result(data, key_lst, multi_key_list):
291+
"""decode结果中的字段"""
292+
for key in key_lst:
293+
if key in data.keys() and data[key]:
294+
data[key] = unquote(data[key])
295+
for multi_key in multi_key_list:
296+
if multi_key[0] in data.keys():
297+
for item in data[multi_key[0]]:
298+
if multi_key[1] in item.keys() and item[multi_key[1]]:
299+
item[multi_key[1]] = unquote(item[multi_key[1]])
300+
return data

qcloud_cos/cos_threadpool.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ def get_result(self):
4343

4444
class SimpleThreadPool:
4545

46-
def __init__(self, num_threads=5):
46+
def __init__(self, num_threads=5, num_queue=0):
4747
self._num_threads = num_threads
48-
self._queue = Queue()
48+
self._queue = Queue(num_queue)
4949
self._lock = Lock()
5050
self._active = False
5151
self._workers = list()

qcloud_cos/demo.py

+4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
from qcloud_cos import CosServiceError
55
from qcloud_cos import CosClientError
66

7+
import logging
8+
79
# 腾讯云COSV5Python SDK, 目前可以支持Python2.6与Python2.7
810

911
# pip安装指南:pip install -U cos-python-sdk-v5
1012

1113
# cos最新可用地域,参照https://www.qcloud.com/document/product/436/6224
1214

15+
logging.basicConfig(level=logging.DEBUG, stream=sys.stdout)
16+
1317
# 设置用户属性, 包括secret_id, secret_key, region
1418
# appid已在配置中移除,请在参数Bucket中带上appid。Bucket由bucketname-appid组成
1519
secret_id = 'AKID15IsskiBQACGbAo6WhgcQbVls7HmuG00' # 替换为用户的secret_id

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def long_description():
1616

1717
setup(
1818
name='cos-python-sdk-v5',
19-
version='1.3.2',
19+
version='1.4.1',
2020
url='https://www.qcloud.com/',
2121
license='MIT',
2222
author='tiedu, lewzylu, channingliu',

qcloud_cos/test.py renamed to ut/test.py

+72-7
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import hashlib
66
import os
77
import requests
8-
from cos_client import CosS3Client
9-
from cos_client import CosConfig
10-
from cos_exception import CosServiceError
8+
from qcloud_cos import CosS3Client
9+
from qcloud_cos import CosConfig
10+
from qcloud_cos import CosServiceError
1111

1212
SECRET_ID = os.environ["SECRET_ID"]
1313
SECRET_KEY = os.environ["SECRET_KEY"]
@@ -297,7 +297,7 @@ def test_delete_multiple_objects():
297297
)
298298
assert response2
299299
objects = {
300-
"Quite": "true",
300+
"Quiet": "true",
301301
"Object": [
302302
{
303303
"Key": file_name1
@@ -311,7 +311,6 @@ def test_delete_multiple_objects():
311311
Bucket=test_bucket,
312312
Delete=objects
313313
)
314-
assert response
315314

316315

317316
def test_create_head_delete_bucket():
@@ -353,7 +352,9 @@ def test_list_objects():
353352
"""列出bucket下的objects"""
354353
response = client.list_objects(
355354
Bucket=test_bucket,
356-
MaxKeys=100
355+
MaxKeys=100,
356+
Prefix='中文',
357+
Delimiter='/'
357358
)
358359
assert response
359360

@@ -589,7 +590,7 @@ def test_upload_empty_file():
589590

590591
def test_copy_10G_file_in_same_region():
591592
"""同园区的拷贝,应该直接用copy_object接口,可以直接秒传"""
592-
copy_source = {'Bucket': 'test01-1252448703', 'Key': '/10G.txt', 'Region': 'ap-beijing-1'}
593+
copy_source = {'Bucket': 'test01-1252448703', 'Key': '10G.txt', 'Region': 'ap-beijing-1'}
593594
response = client.copy(
594595
Bucket='test04-1252448703',
595596
Key='10G.txt',
@@ -610,8 +611,70 @@ def test_use_get_auth():
610611
assert response.status_code == 200
611612

612613

614+
def test_upload_with_server_side_encryption():
615+
"""上传带上加密头部,下载时验证有该头部"""
616+
response = client.put_object(
617+
Bucket=test_bucket,
618+
Key=test_object,
619+
Body='123',
620+
ServerSideEncryption='AES256'
621+
)
622+
assert response['x-cos-server-side-encryption'] == 'AES256'
623+
624+
625+
def test_put_get_bucket_logging():
626+
"""测试bucket的logging服务"""
627+
logging_bucket = 'logging-beijing-1252448703'
628+
logging_config = {
629+
'LoggingEnabled': {
630+
'TargetBucket': logging_bucket,
631+
'TargetPrefix': 'test'
632+
}
633+
}
634+
beijing_conf = CosConfig(
635+
Region="ap-beijing",
636+
Secret_id=SECRET_ID,
637+
Secret_key=SECRET_KEY
638+
)
639+
logging_client = CosS3Client(beijing_conf)
640+
response = logging_client.put_bucket_logging(
641+
Bucket=logging_bucket,
642+
BucketLoggingStatus=logging_config
643+
)
644+
time.sleep(4)
645+
response = logging_client.get_bucket_logging(
646+
Bucket=logging_bucket
647+
)
648+
print response
649+
assert response['LoggingEnabled']['TargetBucket'] == logging_bucket
650+
assert response['LoggingEnabled']['TargetPrefix'] == 'test'
651+
652+
653+
def test_put_object_enable_md5():
654+
"""上传文件,SDK计算content-md5头部"""
655+
file_size = 10
656+
file_name = 'test_object_sdk_caculate_md5.file'
657+
gen_file(file_name, 10)
658+
with open(file_name, 'rb') as f:
659+
etag = get_raw_md5(f.read())
660+
with open(file_name, 'rb') as fp:
661+
put_response = client.put_object(
662+
Bucket=test_bucket,
663+
Body=fp,
664+
Key=file_name,
665+
EnableMD5=True,
666+
CacheControl='no-cache',
667+
ContentDisposition='download.txt'
668+
)
669+
assert etag == put_response['Etag']
670+
if os.path.exists(file_name):
671+
os.remove(file_name)
672+
673+
613674
if __name__ == "__main__":
614675
setUp()
676+
test_put_object_enable_md5()
677+
test_upload_with_server_side_encryption()
615678
test_upload_empty_file()
616679
test_put_get_delete_object_10MB()
617680
test_put_get_versioning()
@@ -620,5 +683,7 @@ def test_use_get_auth():
620683
test_upload_file_multithreading()
621684
test_copy_file_automatically()
622685
test_copy_10G_file_in_same_region()
686+
test_list_objects()
623687
test_use_get_auth()
688+
test_put_get_bucket_logging()
624689
tearDown()

0 commit comments

Comments
 (0)