├── CHANGES.txt ├── LICENSE ├── README.md ├── qcloud_cos ├── __init__.py ├── cos_auth.py ├── cos_client.py ├── cos_config.py ├── cos_cred.py ├── cos_err.py ├── cos_op.py ├── cos_params_check.py └── cos_request.py ├── sample.py └── setup.py /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v3.3 2 | 1 重构python sdk, 封装各种请求, 因此和1.0.0版本不兼容 3 | 2 统一用户的字符类型参数为unicode 4 | 3 支持文件上传覆盖 5 | 4 支持自动根据用户的文件大小选择整个文件上传或者单文件上传 6 | 5 支持文件移动(重命名) 7 | 6 支持更新文件的权限,以及相关的HTTP头 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # python sdk for qcloud cos 2 | python sdk for [腾讯云COS服务] 3 | version: 3.3 4 | 5 | ## 已弃用 - 请升级到 cos-python-sdk-v5 6 | 7 | SDK 依赖的 JSON API 已弃用,请直接使用基于 XML API 的 [cos-python-sdk-v5](https://github.com/tencentyun/cos-python-sdk-v5),或者参照 [升级指引](https://cloud.tencent.com/document/product/436/31356) 升级到新版SDK。 8 | 9 | ## 安装指南 10 | 11 | 请参照 [python sdk wiki](https://www.qcloud.com/doc/product/227/3385) 12 | 13 | 执行 pip install qcloud_cos 14 | 15 | ### 使用python sdk,参照sample.py 16 | ```python 17 | # 设置用户属性, 包括appid, secret_id和secret_key 18 | # 这些属性可以在cos控制台获取(https://console.qcloud.com/cos) 19 | appid = 100000 # 替换为用户的appid 20 | secret_id = u'xxxxxxxx' # 替换为用户的secret_id 21 | secret_key = u'xxxxxxx' # 替换为用户的secret_key 22 | cos_client = CosClient(appid, secret_id, secret_key) 23 | 24 | # 设置要操作的bucket 25 | bucket = u'mybucket' 26 | 27 | ############################################################################ 28 | # 文件操作 # 29 | ############################################################################ 30 | # 1. 上传文件(默认不覆盖) 31 | # 将本地的local_file_1.txt上传到bucket的根分区下,并命名为sample_file.txt 32 | # 默认不覆盖, 如果cos上文件存在,则会返回错误 33 | request = UploadFileRequest(bucket, u'/sample_file.txt', u'local_file_1.txt') 34 | upload_file_ret = cos_client.upload_file(request) 35 | print 'upload file ret:', repr(upload_file_ret) 36 | 37 | # 2. 上传文件(覆盖文件) 38 | # 将本地的local_file_2.txt上传到bucket的根分区下,覆盖已上传的sample_file.txt 39 | request = UploadFileRequest(bucket, u'/sample_file.txt', u'local_file_2.txt') 40 | request.set_insert_only(0) # 设置允许覆盖 41 | upload_file_ret = cos_client.upload_file(request) 42 | print 'overwrite file ret:', repr(upload_file_ret) 43 | 44 | # 3. 获取文件属性 45 | request = StatFileRequest(bucket, u'/sample_file.txt') 46 | stat_file_ret = cos_client.stat_file(request) 47 | print 'stat file ret:', repr(stat_file_ret) 48 | 49 | # 4. 更新文件属性 50 | request = UpdateFileRequest(bucket, u'/sample_file.txt') 51 | 52 | request.set_biz_attr(u'这是个demo文件') # 设置文件biz_attr属性 53 | request.set_authority(u'eWRPrivate') # 设置文件的权限 54 | request.set_cache_control(u'cache_xxx') # 设置Cache-Control 55 | request.set_content_type(u'application/text') # 设置Content-Type 56 | request.set_content_disposition(u'ccccxxx.txt') # 设置Content-Disposition 57 | request.set_content_language(u'english') # 设置Content-Language 58 | request.set_x_cos_meta(u'x-cos-meta-xxx', u'xxx') # 设置自定义的x-cos-meta-属性 59 | request.set_x_cos_meta(u'x-cos-meta-yyy', u'yyy') # 设置自定义的x-cos-meta-属性 60 | 61 | update_file_ret = cos_client.update_file(request) 62 | print 'update file ret:', repr(update_file_ret) 63 | 64 | # 5. 更新后再次获取文件属性 65 | request = StatFileRequest(bucket, u'/sample_file.txt') 66 | stat_file_ret = cos_client.stat_file(request) 67 | print 'stat file ret:', repr(stat_file_ret) 68 | 69 | # 6. 移动文件, 将sample_file.txt移动位sample_file_move.txt 70 | request = MoveFileRequest(bucket, u'/sample_file.txt', u'/sample_file_move.txt') 71 | stat_file_ret = cos_client.move_file(request) 72 | print 'move file ret:', repr(stat_file_ret) 73 | 74 | # 7. 删除文件 75 | request = DelFileRequest(bucket, u'/sample_file_move.txt') 76 | del_ret = cos_client.del_file(request) 77 | print 'del file ret:', repr(del_ret) 78 | 79 | ############################################################################ 80 | # 目录操作 # 81 | ############################################################################ 82 | # 1. 生成目录, 目录名为sample_folder 83 | request = CreateFolderRequest(bucket, u'/sample_folder/') 84 | create_folder_ret = cos_client.create_folder(request) 85 | print 'create folder ret:', create_folder_ret 86 | 87 | # 2. 更新目录的biz_attr属性 88 | request = UpdateFolderRequest(bucket, u'/sample_folder/', u'这是一个测试目录') 89 | update_folder_ret = cos_client.update_folder(request) 90 | print 'update folder ret:', repr(update_folder_ret) 91 | 92 | # 3. 获取目录属性 93 | request = StatFolderRequest(bucket, u'/sample_folder/') 94 | stat_folder_ret = cos_client.stat_folder(request) 95 | print 'stat folder ret:', repr(stat_folder_ret) 96 | 97 | # 4. list目录, 获取目录下的成员 98 | request = ListFolderRequest(bucket, u'/sample_folder/') 99 | list_folder_ret = cos_client.list_folder(request) 100 | print 'list folder ret:', repr(list_folder_ret) 101 | 102 | # 5. 删除目录 103 | request = DelFolderRequest(bucket, u'/sample_folder/') 104 | delete_folder_ret = cos_client.del_folder(request) 105 | print 'delete folder ret:', repr(delete_folder_ret) 106 | ``` 107 | -------------------------------------------------------------------------------- /qcloud_cos/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | from .cos_client import CosClient 4 | from .cos_client import CosConfig 5 | from .cos_client import CredInfo 6 | from .cos_request import UploadFileRequest 7 | from .cos_request import UploadSliceFileRequest 8 | from .cos_request import UpdateFileRequest 9 | from .cos_request import UpdateFolderRequest 10 | from .cos_request import DelFolderRequest 11 | from .cos_request import DelFileRequest 12 | from .cos_request import CreateFolderRequest 13 | from .cos_request import StatFileRequest 14 | from .cos_request import StatFolderRequest 15 | from .cos_request import MoveFileRequest 16 | from .cos_request import ListFolderRequest 17 | from .cos_auth import Auth 18 | from .cos_cred import CredInfo 19 | -------------------------------------------------------------------------------- /qcloud_cos/cos_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import random 5 | import time 6 | import urllib 7 | import hmac 8 | import hashlib 9 | import binascii 10 | import base64 11 | import cos_cred 12 | 13 | class Auth(object): 14 | def __init__(self, cred): 15 | self.cred = cred 16 | 17 | def app_sign(self, bucket, cos_path, expired, upload_sign=True): 18 | appid = self.cred.get_appid() 19 | bucket = bucket.encode('utf8') 20 | secret_id = self.cred.get_secret_id().encode('utf8') 21 | now = int(time.time()) 22 | rdm = random.randint(0, 999999999) 23 | cos_path = urllib.quote(cos_path.encode('utf8'), '~/') 24 | if upload_sign: 25 | fileid = '/%s/%s%s' % (appid, bucket, cos_path) 26 | else: 27 | fileid = cos_path 28 | sign_tuple = (appid, secret_id, expired, now, rdm, fileid, bucket) 29 | 30 | plain_text = 'a=%s&k=%s&e=%d&t=%d&r=%d&f=%s&b=%s' % sign_tuple 31 | secret_key = self.cred.get_secret_key().encode('utf8') 32 | sha1_hmac = hmac.new(secret_key, plain_text, hashlib.sha1) 33 | hmac_digest = sha1_hmac.hexdigest() 34 | hmac_digest = binascii.unhexlify(hmac_digest) 35 | sign_hex = hmac_digest + plain_text 36 | sign_base64 = base64.b64encode(sign_hex) 37 | return sign_base64 38 | 39 | # 单次签名(针对删除和更新操作) 40 | # bucket: bucket名称 41 | # cos_path: 要操作的cos路径, 以'/'开始 42 | def sign_once(self, bucket, cos_path): 43 | return self.app_sign(bucket, cos_path, 0) 44 | 45 | # 多次签名(针对上传文件,创建目录, 获取文件目录属性, 拉取目录列表) 46 | # bucket: bucket名称 47 | # cos_path: 要操作的cos路径, 以'/'开始 48 | # expired: 签名过期时间, UNIX时间戳 49 | # 如想让签名在30秒后过期, 即可将expired设成当前时间加上30秒 50 | def sign_more(self, bucket, cos_path, expired): 51 | return self.app_sign(bucket, cos_path, expired) 52 | 53 | # 下载签名(用于获取后拼接成下载链接,下载私有bucket的文件) 54 | # bucket: bucket名称 55 | # cos_path: 要下载的cos文件路径, 以'/'开始 56 | # expired: 签名过期时间, UNIX时间戳 57 | # 如想让签名在30秒后过期, 即可将expired设成当前时间加上30秒 58 | def sign_download(self, bucket, cos_path, expired): 59 | return self.app_sign(bucket, cos_path, expired, False) 60 | -------------------------------------------------------------------------------- /qcloud_cos/cos_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import requests 5 | from cos_cred import CredInfo 6 | from cos_config import CosConfig 7 | from cos_op import FileOp 8 | from cos_op import FolderOp 9 | from cos_request import UploadFileRequest 10 | from cos_request import UploadSliceFileRequest 11 | from cos_request import UpdateFileRequest 12 | from cos_request import UpdateFolderRequest 13 | from cos_request import DelFileRequest 14 | from cos_request import MoveFileRequest 15 | from cos_request import DelFolderRequest 16 | from cos_request import CreateFolderRequest 17 | from cos_request import StatFolderRequest 18 | from cos_request import StatFileRequest 19 | from cos_request import ListFolderRequest 20 | try: 21 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 22 | requests.packages.urllib3.disable_warnings(InsecureRequestWarning) 23 | except ImportError: 24 | pass 25 | 26 | ################################################################################ 27 | # Cos客户端类 # 28 | ################################################################################ 29 | class CosClient(object): 30 | # 设置用户的相关信息 31 | def __init__(self, appid, secret_id, secret_key): 32 | self._cred = CredInfo(appid, secret_id, secret_key) 33 | self._config = CosConfig() 34 | self._http_session = requests.session() 35 | self._file_op = FileOp(self._cred, self._config, self._http_session) 36 | self._folder_op = FolderOp(self._cred, self._config, self._http_session) 37 | 38 | # 设置config 39 | def set_config(self, config): 40 | assert isinstance(config, CosConfig) 41 | self._config = config 42 | self._file_op.set_config(config) 43 | self._folder_op.set_config(config) 44 | 45 | # 获取config 46 | def get_config(self): 47 | return self._config 48 | 49 | # 设置用户的身份信息 50 | def set_cred(self, cred): 51 | assert isinstance(cred, CredInfo) 52 | self._cred = cred 53 | self._file_op.set_cred(cred) 54 | self._folder_op.set_cred(cred) 55 | 56 | # 获取用户的相关信息 57 | def get_cred(self): 58 | return self._cred 59 | 60 | # 上传文件(自动根据文件大小,选择上传策略, 强烈推荐使用) 61 | # 上传策略: 8MB以下适用单文件上传, 8MB(含)适用分片上传 62 | def upload_file(self, request): 63 | assert isinstance(request, UploadFileRequest) 64 | return self._file_op.upload_file(request) 65 | 66 | # 单文件上传接口, 适用用小文件8MB以下 67 | # 最大不得超过20MB, 否则会返回参数错误 68 | def upload_single_file(self, request): 69 | assert isinstance(request, UploadFileRequest) 70 | return self._file_op.upload_single_file(request) 71 | 72 | # 分片上传接口, 适用于大文件8MB及以上 73 | def upload_slice_file(self, request): 74 | assert isinstance(request, UploadSliceFileRequest) 75 | return self._file_op.upload_slice_file(request) 76 | 77 | # 删除文件 78 | def del_file(self, request): 79 | assert isinstance(request, DelFileRequest) 80 | return self._file_op.del_file(request) 81 | 82 | # 获取文件属性 83 | def stat_file(self, request): 84 | assert isinstance(request, StatFileRequest) 85 | return self._file_op.stat_file(request) 86 | 87 | # 更新文件属性 88 | def update_file(self, request): 89 | assert isinstance(request, UpdateFileRequest) 90 | return self._file_op.update_file(request) 91 | 92 | # 移动文件 93 | def move_file(self, request): 94 | assert isinstance(request, MoveFileRequest) 95 | return self._file_op.move_file(request) 96 | 97 | # 创建目录 98 | def create_folder(self, request): 99 | assert isinstance(request, CreateFolderRequest) 100 | return self._folder_op.create_folder(request) 101 | 102 | # 删除目录 103 | def del_folder(self, request): 104 | assert isinstance(request, DelFolderRequest) 105 | return self._folder_op.del_folder(request) 106 | 107 | # 获取folder属性请求 108 | def stat_folder(self, request): 109 | assert isinstance(request, StatFolderRequest) 110 | return self._folder_op.stat_folder(request) 111 | 112 | # 更新目录属性 113 | def update_folder(self, request): 114 | assert isinstance(request, UpdateFolderRequest) 115 | return self._folder_op.update_folder(request) 116 | 117 | # 获取目录下的文件和目录列表 118 | def list_folder(self, request): 119 | assert isinstance(request, ListFolderRequest) 120 | return self._folder_op.list_folder(request) 121 | -------------------------------------------------------------------------------- /qcloud_cos/cos_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | ################################################################################ 5 | # CosConfig 有关cos的配置 6 | ################################################################################ 7 | class CosConfig(object): 8 | def __init__(self): 9 | self._end_point = 'http://web.file.myqcloud.com/files/v1' 10 | self._user_agent = 'cos-python-sdk-v3.3' 11 | self._timeout = 30 12 | self._sign_expired = 300 13 | 14 | # 设置COS的域名地址 15 | def set_end_point(self, end_point): 16 | self._end_point = end_point 17 | 18 | # 获取域名地址 19 | def get_end_point(self): 20 | return self._end_point 21 | 22 | # 获取HTTP头中的user_agent 23 | def get_user_agent(self): 24 | return self._user_agent 25 | 26 | # 设置连接超时, 单位秒 27 | def set_timeout(self, time_out): 28 | assert isinstance(time_out, int) 29 | self._timeout = time_out 30 | 31 | # 获取连接超时,单位秒 32 | def get_timeout(self): 33 | return self._timeout 34 | 35 | # 设置签名过期时间, 单位秒 36 | def set_sign_expired(self, expired): 37 | assert isinstance(expired, int) 38 | self._sign_expired = expired 39 | 40 | # 获取签名过期时间, 单位秒 41 | def get_sign_expired(self): 42 | return self._sign_expired 43 | 44 | # 打开https 45 | def enable_https(self): 46 | self._end_point = 'https://web.file.myqcloud.com/files/v1' 47 | -------------------------------------------------------------------------------- /qcloud_cos/cos_cred.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | from cos_params_check import ParamCheck 5 | 6 | ################################################################################ 7 | # CredInfo用户的身份信息 8 | ################################################################################ 9 | class CredInfo(object): 10 | def __init__(self, appid, secret_id, secret_key): 11 | self._appid = appid 12 | self._secret_id = secret_id 13 | self._secret_key = secret_key 14 | self._param_check = ParamCheck() 15 | 16 | 17 | def get_appid(self): 18 | return self._appid 19 | 20 | def get_secret_id(self): 21 | return self._secret_id 22 | 23 | def get_secret_key(self): 24 | return self._secret_key 25 | 26 | def check_params_valid(self): 27 | if not self._param_check.check_param_int('appid', self._appid): 28 | return False 29 | if not self._param_check.check_param_unicode('secret_id', self._secret_id): 30 | return False 31 | return self._param_check.check_param_unicode('secret_key', self._secret_key) 32 | 33 | # 获取错误信息 34 | def get_err_tips(self): 35 | return self._param_check.get_err_tips() 36 | -------------------------------------------------------------------------------- /qcloud_cos/cos_err.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | ################################################################################ 5 | # sdk错误码 6 | ################################################################################ 7 | class CosErr(object): 8 | PARAMS_ERROR = -1 # 参数错误 9 | NETWORK_ERROR = -2 # 网络错误 10 | SERVER_ERROR = -3 # server端返回错误 11 | UNKNOWN_ERROR = -4 # 未知错误 12 | 13 | @classmethod 14 | def get_err_msg(cls, errcode, err_info): 15 | err_msg = {} 16 | err_msg[u'code'] = errcode 17 | err_msg[u'message'] = err_info 18 | return err_msg 19 | -------------------------------------------------------------------------------- /qcloud_cos/cos_op.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import os 5 | import time 6 | import json 7 | import hashlib 8 | import urllib 9 | import cos_auth 10 | import cos_config 11 | from cos_err import CosErr 12 | from cos_request import UploadFileRequest 13 | from cos_request import UploadSliceFileRequest 14 | from cos_request import UpdateFileRequest 15 | from cos_request import DelFileRequest 16 | from cos_request import StatFileRequest 17 | from cos_request import MoveFileRequest 18 | from cos_request import CreateFolderRequest 19 | from cos_request import UpdateFolderRequest 20 | from cos_request import StatFolderRequest 21 | from cos_request import DelFolderRequest 22 | from cos_request import ListFolderRequest 23 | 24 | ################################################################################ 25 | # BaseOp基本操作类型 # 26 | ################################################################################ 27 | class BaseOp(object): 28 | # cred: 用户的身份信息 29 | # config: cos_config配置类 30 | # http_session: http 会话 31 | # expired_period: 签名过期时间, 单位秒 32 | def __init__(self, cred, config, http_session): 33 | self._cred = cred 34 | self._config = config 35 | self._http_session = http_session 36 | self._expired_period = self._config.get_sign_expired() 37 | 38 | # 设置用户的身份信息 39 | def set_cred(self, cred): 40 | self._cred = cred 41 | 42 | # 设置config 43 | def set_config(self, config): 44 | self._config = config 45 | self._expired_period = self._config.get_sign_expired() 46 | 47 | # 生成url 48 | def _build_url(self, bucket, cos_path): 49 | bucket = bucket.encode('utf8') 50 | end_point = self._config.get_end_point().rstrip('/').encode('utf8') 51 | appid = self._cred.get_appid() 52 | cos_path = urllib.quote(cos_path.encode('utf8'), '~/') 53 | url = '%s/%s/%s%s' % (end_point, appid, bucket, cos_path) 54 | return url 55 | 56 | # 发送http请求 57 | def send_request(self, method, bucket, cos_path, **args): 58 | url = self._build_url(bucket, cos_path) 59 | http_resp = {} 60 | try: 61 | if method == 'POST': 62 | http_resp = self._http_session.post(url, verify=False, **args) 63 | else: 64 | http_resp = self._http_session.get(url, verify=False, **args) 65 | 66 | status_code = http_resp.status_code 67 | if (status_code == 200 or status_code == 400): 68 | return http_resp.json() 69 | else: 70 | err_detail = 'url:%s, status_code:%d' % (url, status_code) 71 | return CosErr.get_err_msg(CosErr.NETWORK_ERROR, err_detail) 72 | except Exception as e: 73 | err_detail = 'url:%s, exception:%s' % (url, repr(e)) 74 | return CosErr.get_err_msg(CosErr.SERVER_ERROR, err_detail) 75 | 76 | # 检查用户输入参数, 检查通过返回None, 否则返回一个代表错误原因的dict 77 | def _check_params(self, request): 78 | if not self._cred.check_params_valid(): 79 | return CosErr.get_err_msg(CosErr.PARAMS_ERROR, self._cred.get_err_tips()) 80 | if not request.check_params_valid(): 81 | return CosErr.get_err_msg(CosErr.PARAMS_ERROR, request.get_err_tips()) 82 | return None 83 | 84 | # 删除文件或者目录, is_file_op为True表示是文件操作 85 | def del_base(self, request): 86 | check_params_ret = self._check_params(request) 87 | if check_params_ret != None: 88 | return check_params_ret 89 | 90 | auth = cos_auth.Auth(self._cred) 91 | bucket = request.get_bucket_name() 92 | cos_path = request.get_cos_path() 93 | sign = auth.sign_once(bucket, cos_path) 94 | 95 | http_header = {} 96 | http_header['Authorization'] = sign 97 | http_header['Content-Type'] = 'application/json' 98 | http_header['User-Agent'] = self._config.get_user_agent() 99 | 100 | http_body = {} 101 | http_body['op'] = 'delete' 102 | 103 | timeout = self._config.get_timeout() 104 | return self.send_request('POST', bucket, cos_path, headers=http_header, 105 | data=json.dumps(http_body), timeout=timeout) 106 | 107 | # 获取文件和目录的属性 108 | def stat_base(self, request): 109 | check_params_ret = self._check_params(request) 110 | if check_params_ret != None: 111 | return check_params_ret 112 | 113 | auth = cos_auth.Auth(self._cred) 114 | bucket = request.get_bucket_name() 115 | cos_path = request.get_cos_path() 116 | expired = int(time.time()) + self._expired_period 117 | sign = auth.sign_more(bucket, cos_path, expired) 118 | 119 | http_header = {} 120 | http_header['Authorization'] = sign 121 | http_header['User-Agent'] = self._config.get_user_agent() 122 | 123 | http_body = {} 124 | http_body['op'] = 'stat' 125 | 126 | timeout = self._config.get_timeout() 127 | return self.send_request('GET', bucket, cos_path, headers=http_header, 128 | params=http_body, timeout=timeout) 129 | 130 | ################################################################################ 131 | # FileOp 文件相关操作 # 132 | ################################################################################ 133 | class FileOp(BaseOp): 134 | # cred: 用户的身份信息 135 | # config: cos_config配置类 136 | # http_session: http 会话 137 | def __init__(self, cred, config, http_session): 138 | BaseOp.__init__(self, cred, config, http_session) 139 | # 单文件上传的最大上限是20MB 140 | self.max_single_file = 20 * 1024 * 1024 141 | 142 | # 获取content的sha1 143 | def _sha1_content(self, content): 144 | sha1_obj = hashlib.sha1() 145 | sha1_obj.update(content) 146 | return sha1_obj.hexdigest() 147 | 148 | # 获取文件的sha1 149 | def _sha1_file(self, file_path): 150 | sha1_obj = hashlib.sha1() 151 | with open(file_path, 'rb') as f: 152 | while True: 153 | block_data = f.read(4096) 154 | if not block_data: 155 | break 156 | sha1_obj.update(block_data) 157 | return sha1_obj.hexdigest() 158 | 159 | # 更新文件 160 | def update_file(self, request): 161 | assert isinstance(request, UpdateFileRequest) 162 | check_params_ret = self._check_params(request) 163 | if check_params_ret != None: 164 | return check_params_ret 165 | 166 | auth = cos_auth.Auth(self._cred) 167 | bucket = request.get_bucket_name() 168 | cos_path = request.get_cos_path() 169 | sign = auth.sign_once(bucket, cos_path) 170 | 171 | http_header = {} 172 | http_header['Authorization'] = sign 173 | http_header['Content-Type'] = 'application/json' 174 | http_header['User-Agent'] = self._config.get_user_agent() 175 | 176 | http_body = {} 177 | http_body['op'] = 'update' 178 | 179 | update_flag = request.get_flag() 180 | http_body['flag'] = str(update_flag) 181 | if (update_flag & 0x01 != 0): 182 | http_body['biz_attr'] = request.get_biz_attr() 183 | if (update_flag & 0x80 != 0): 184 | http_body['authority'] = request.get_authority() 185 | if (update_flag & 0x40 != 0): 186 | http_body['custom_headers'] = request.get_custom_headers() 187 | 188 | timeout = self._config.get_timeout() 189 | return self.send_request('POST', bucket, cos_path, headers=http_header, 190 | data=json.dumps(http_body), timeout=timeout) 191 | 192 | # 删除文件 193 | def del_file(self, request): 194 | assert isinstance(request, DelFileRequest) 195 | return self.del_base(request) 196 | 197 | # 获取文件的属性 198 | def stat_file(self, request): 199 | assert isinstance(request, StatFileRequest) 200 | return self.stat_base(request) 201 | 202 | # 移动文件 203 | def move_file(self, request): 204 | assert isinstance(request, MoveFileRequest) 205 | check_params_ret = self._check_params(request) 206 | if check_params_ret != None: 207 | return check_params_ret 208 | 209 | auth = cos_auth.Auth(self._cred) 210 | bucket = request.get_bucket_name() 211 | cos_path = request.get_cos_path() 212 | expired = int(time.time()) + self._expired_period 213 | sign = auth.sign_more(bucket, cos_path, expired) 214 | 215 | http_header = {} 216 | http_header['Authorization'] = sign 217 | http_header['Content-Type'] = 'application/json' 218 | http_header['User-Agent'] = self._config.get_user_agent() 219 | 220 | http_body = {} 221 | http_body['op'] = 'move' 222 | http_body['dest_fileid'] = request.get_dst_cos_path() 223 | http_body['to_over_write'] = request.get_over_write() 224 | 225 | timeout = self._config.get_timeout() 226 | return self.send_request('POST', bucket, cos_path, headers=http_header, 227 | data=json.dumps(http_body), timeout=timeout) 228 | 229 | # 上传文件, 根据用户的文件大小,选择单文件上传和分片上传策略 230 | def upload_file(self, request): 231 | assert isinstance(request, UploadFileRequest) 232 | check_params_ret = self._check_params(request) 233 | if check_params_ret != None: 234 | return check_params_ret 235 | 236 | local_path = request.get_local_path() 237 | file_size = os.path.getsize(local_path) 238 | 239 | suit_single_file_zie = 8 * 1024 * 1024 240 | if (file_size < suit_single_file_zie): 241 | return self.upload_single_file(request) 242 | else: 243 | bucket = request.get_bucket_name() 244 | cos_path = request.get_cos_path() 245 | local_path = request.get_local_path() 246 | slice_size = 1024 * 1024 247 | biz_attr = request.get_biz_attr() 248 | upload_slice_request = UploadSliceFileRequest(bucket, cos_path, 249 | local_path, slice_size, biz_attr) 250 | upload_slice_request.set_insert_only(request.get_insert_only()) 251 | return self.upload_slice_file(upload_slice_request) 252 | 253 | # 单文件上传 254 | def upload_single_file(self, request): 255 | assert isinstance(request, UploadFileRequest) 256 | check_params_ret = self._check_params(request) 257 | if check_params_ret != None: 258 | return check_params_ret 259 | 260 | local_path = request.get_local_path() 261 | file_size = os.path.getsize(local_path) 262 | # 判断文件是否超过单文件最大上限, 如果超过则返回错误 263 | # 并提示用户使用别的接口 264 | if file_size > self.max_single_file: 265 | return CosErr.get_err_msg(CosErr.NETWORK_ERROR, 266 | 'file is to big, please use upload_file interface') 267 | 268 | auth = cos_auth.Auth(self._cred) 269 | bucket = request.get_bucket_name() 270 | cos_path = request.get_cos_path() 271 | expired = int(time.time()) + self._expired_period 272 | sign = auth.sign_more(bucket, cos_path, expired) 273 | 274 | http_header = {} 275 | http_header['Authorization'] = sign 276 | http_header['User-Agent'] = self._config.get_user_agent() 277 | 278 | with open(local_path, 'rb') as f: 279 | file_content = f.read() 280 | 281 | http_body = {} 282 | http_body['op'] = 'upload' 283 | http_body['filecontent'] = file_content 284 | http_body['sha'] = self._sha1_content(file_content) 285 | http_body['biz_attr'] = request.get_biz_attr() 286 | http_body['insertOnly'] = str(request.get_insert_only()) 287 | 288 | timeout = self._config.get_timeout() 289 | return self.send_request('POST', bucket, cos_path, headers=http_header, 290 | files=http_body, timeout=timeout) 291 | 292 | # 分片文件上传(串行) 293 | def upload_slice_file(self, request): 294 | assert isinstance(request, UploadSliceFileRequest) 295 | check_params_ret = self._check_params(request) 296 | if check_params_ret != None: 297 | return check_params_ret 298 | 299 | control_ret = self._upload_slice_control(request) 300 | # 表示控制分片已经产生错误信息 301 | if control_ret[u'code'] != 0: 302 | return control_ret 303 | # 命中秒传 304 | if control_ret[u'data'].has_key(u'access_url'): 305 | return control_ret 306 | 307 | bucket = request.get_bucket_name() 308 | cos_path = request.get_cos_path() 309 | local_path = request.get_local_path() 310 | file_size = os.path.getsize(local_path) 311 | slice_size = control_ret[u'data'][u'slice_size'] 312 | offset = control_ret[u'data'][u'offset'] 313 | session = control_ret[u'data'][u'session'] 314 | 315 | with open(local_path, 'rb') as local_file: 316 | local_file.seek(offset) 317 | while offset < file_size: 318 | file_content = local_file.read(slice_size) 319 | retry_count = 0 320 | max_retry = 3 321 | # 如果分片数据上传发生错误, 则进行重试,默认3次 322 | while retry_count < max_retry: 323 | data_ret = self._upload_slice_data(bucket, cos_path, 324 | file_content, session, offset) 325 | if data_ret[u'code'] == 0: 326 | if data_ret[u'data'].has_key(u'access_url'): 327 | return data_ret 328 | else: 329 | break 330 | else: 331 | retry_count += 1 332 | 333 | if retry_count == max_retry: 334 | return data_ret 335 | else: 336 | offset += slice_size 337 | return data_ret 338 | 339 | # 串行分片第一步, 上传控制分片 340 | def _upload_slice_control(self, request): 341 | auth = cos_auth.Auth(self._cred) 342 | bucket = request.get_bucket_name() 343 | cos_path = request.get_cos_path() 344 | expired = int(time.time()) + self._expired_period 345 | sign = auth.sign_more(bucket, cos_path, expired) 346 | 347 | http_header = {} 348 | http_header['Authorization'] = sign 349 | http_header['User-Agent'] = self._config.get_user_agent() 350 | 351 | local_path = request.get_local_path() 352 | sha1_digest = self._sha1_file(local_path) 353 | file_size = os.path.getsize(local_path) 354 | slice_size = request.get_slice_size() 355 | biz_atrr = request.get_biz_attr() 356 | 357 | http_body = {} 358 | http_body['op'] = 'upload_slice' 359 | http_body['sha'] = sha1_digest 360 | http_body['filesize'] = str(file_size) 361 | http_body['slice_size'] = str(slice_size) 362 | http_body['biz_attr'] = request.get_biz_attr() 363 | http_body['insertOnly'] = str(request.get_insert_only()) 364 | 365 | timeout = self._config.get_timeout() 366 | return self.send_request('POST', bucket, cos_path, headers=http_header, 367 | files=http_body, timeout=timeout) 368 | 369 | # 串行分片第二步, 上传数据分片 370 | def _upload_slice_data(self, bucket, cos_path, file_content, session, offset): 371 | auth = cos_auth.Auth(self._cred) 372 | expired = int(time.time()) + self._expired_period 373 | sign = auth.sign_more(bucket, cos_path, expired) 374 | 375 | http_header = {} 376 | http_header['Authorization'] = sign 377 | http_header['User-Agent'] = self._config.get_user_agent() 378 | 379 | http_body = {} 380 | http_body['op'] = 'upload_slice' 381 | http_body['filecontent'] = file_content 382 | http_body['session'] = session 383 | http_body['offset'] = str(offset) 384 | 385 | timeout = self._config.get_timeout() 386 | return self.send_request('POST', bucket, cos_path, headers=http_header, 387 | files=http_body, timeout=timeout) 388 | 389 | ################################################################################ 390 | # FolderOp 目录相关操作 # 391 | ################################################################################ 392 | class FolderOp(BaseOp): 393 | def __init__(self, cred, config, http_session): 394 | BaseOp.__init__(self, cred, config, http_session) 395 | 396 | # 更新目录 397 | def update_folder(self, request): 398 | assert isinstance(request, UpdateFolderRequest) 399 | check_params_ret = self._check_params(request) 400 | if check_params_ret != None: 401 | return check_params_ret 402 | 403 | auth = cos_auth.Auth(self._cred) 404 | bucket = request.get_bucket_name() 405 | cos_path = request.get_cos_path() 406 | sign = auth.sign_once(bucket, cos_path) 407 | 408 | http_header = {} 409 | http_header['Authorization'] = sign 410 | http_header['Content-Type'] = 'application/json' 411 | http_header['User-Agent'] = self._config.get_user_agent() 412 | 413 | http_body = {} 414 | http_body['op'] = 'update' 415 | http_body['biz_attr'] = request.get_biz_attr() 416 | 417 | timeout = self._config.get_timeout() 418 | return self.send_request('POST', bucket, cos_path, headers=http_header, 419 | data=json.dumps(http_body), timeout=timeout) 420 | 421 | # 删除目录 422 | def del_folder(self, request): 423 | assert isinstance(request, DelFolderRequest) 424 | return self.del_base(request) 425 | 426 | # 获取目录属性 427 | def stat_folder(self, request): 428 | assert isinstance(request, StatFolderRequest) 429 | return self.stat_base(request) 430 | 431 | # 创建目录 432 | def create_folder(self, request): 433 | assert isinstance(request, CreateFolderRequest) 434 | check_params_ret = self._check_params(request) 435 | if check_params_ret != None: 436 | return check_params_ret 437 | 438 | auth = cos_auth.Auth(self._cred) 439 | bucket = request.get_bucket_name() 440 | cos_path = request.get_cos_path() 441 | expired = int(time.time()) + self._expired_period 442 | sign = auth.sign_more(bucket, cos_path, expired) 443 | 444 | http_header = {} 445 | http_header['Authorization'] = sign 446 | http_header['Content-Type'] = 'application/json' 447 | http_header['User-Agent'] = self._config.get_user_agent() 448 | 449 | http_body = {} 450 | http_body['op'] = 'create' 451 | http_body['biz_attr'] = request.get_biz_attr() 452 | 453 | timeout = self._config.get_timeout() 454 | return self.send_request('POST', bucket, cos_path, headers=http_header, 455 | data=json.dumps(http_body), timeout=timeout) 456 | 457 | # list目录 458 | def list_folder(self, request): 459 | assert isinstance(request, ListFolderRequest) 460 | check_params_ret = self._check_params(request) 461 | if check_params_ret != None: 462 | return check_params_ret 463 | 464 | http_body = {} 465 | http_body['op'] = 'list' 466 | http_body['num'] = request.get_num() 467 | http_body['pattern'] = request.get_pattern() 468 | http_body['order'] = request.get_order() 469 | http_body['context'] = request.get_context() 470 | http_body['prefix'] = request.get_prefix() 471 | 472 | auth = cos_auth.Auth(self._cred) 473 | bucket = request.get_bucket_name() 474 | list_path = request.get_cos_path() + request.get_prefix() 475 | expired = int(time.time()) + self._expired_period 476 | sign = auth.sign_more(bucket, list_path, expired) 477 | 478 | http_header = {} 479 | http_header['Authorization'] = sign 480 | http_header['User-Agent'] = self._config.get_user_agent() 481 | 482 | timeout = self._config.get_timeout() 483 | return self.send_request('GET', bucket, list_path, headers=http_header, 484 | params=http_body, timeout=timeout) 485 | -------------------------------------------------------------------------------- /qcloud_cos/cos_params_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | import os 4 | import re 5 | 6 | ################################################################################ 7 | # BaseRequest基本类型的请求 # 8 | ################################################################################ 9 | class ParamCheck(object): 10 | def __init__(self): 11 | self._err_tips=u'' 12 | 13 | # 获取错误信息 14 | def get_err_tips(self): 15 | return self._err_tips 16 | 17 | # 检查参数是否是unicode 18 | # param_name 参数名 19 | # param_value 参数值 20 | def check_param_unicode(self, param_name, param_value): 21 | if param_value == None: 22 | self._err_tips = param_name + ' is None!' 23 | return False 24 | if not isinstance(param_value, unicode): 25 | self._err_tips = param_name + ' is not unicode!' 26 | return False 27 | return True 28 | 29 | # 检查参数是否是int 30 | # param_name 参数名 31 | # param_value 参数值 32 | def check_param_int(self, param_name, param_value): 33 | if param_value == None: 34 | self._err_tips = param_name + ' is None!' 35 | return False 36 | if not isinstance(param_value, int): 37 | self._err_tips = param_name + ' is not int!' 38 | return False 39 | return True 40 | 41 | # 检查cos_path是否合法, 必须以/开始 42 | # 文件路径则不能以/结束, 目录路径必须以/结束 43 | # 路径合法返回True, 否则返回False 44 | def check_cos_path_valid(self, cos_path, is_file_path): 45 | if cos_path[0] != u'/': 46 | self._err_tips = 'cos path must start with /' 47 | return False 48 | 49 | last_letter = cos_path[len(cos_path) - 1] 50 | if is_file_path and last_letter == u'/': 51 | self._err_tips = 'for file operation, cos_path must not end with /' 52 | return False 53 | elif not is_file_path and last_letter != u'/': 54 | self._err_tips = 'for folder operation, cos_path must end with /' 55 | return False 56 | else: 57 | pass 58 | 59 | illegal_letters = ['?', '*', ':', '|', '\\', '<', '>', '"'] 60 | for illegal_letter in illegal_letters: 61 | if cos_path.find(illegal_letter) != -1: 62 | self._err_tips = 'cos path contain illegal letter %s' % illegal_letter 63 | return False 64 | 65 | pattern = re.compile(r'/(\s*)/') 66 | if pattern.search(cos_path): 67 | self._err_tips = 'cos path contain illegal letter / /' 68 | return False 69 | return True 70 | 71 | # 检查不是cos的跟路基 72 | # 不等进行根路径操作的有 1 update 2 cretate 3 delete 73 | def check_not_cos_root(self, cos_path): 74 | if cos_path == u'/': 75 | self._err_tips = 'bucket operation is not supported by sdk,' 76 | ' please use cos console: https://console.qcloud.com/cos' 77 | return False 78 | else: 79 | return True 80 | 81 | # 检查本地文件有效(存在并且可读) 82 | def check_local_file_valid(self, local_path): 83 | if not os.path.exists(local_path): 84 | self._err_tips = 'local_file %s not exist!' % local_path 85 | return False 86 | if not os.path.isfile(local_path): 87 | self._err_tips = 'local_file %s is not regular file!' % local_path 88 | return False 89 | if not os.access(local_path, os.R_OK): 90 | self._err_tips = 'local_file %s is not readable!' % local_path 91 | return False 92 | return True 93 | 94 | # 检查分片大小有效 95 | def check_slice_size(self, slice_size): 96 | min_size = 512 * 1024 # 512KB 97 | max_size = 20 * 1024 * 1024 # 20MB 98 | if slice_size >= min_size and slice_size <= max_size: 99 | return True 100 | else: 101 | self._err_tips = 'slice_size is invalid, only accept [%d, %d]' \ 102 | % (min_size, max_size) 103 | return False 104 | 105 | # 检查文件上传的insert_only参数 106 | def check_insert_only(self, insert_only): 107 | if insert_only != 1 and insert_only != 0: 108 | self._err_tips = 'insert_only only support 0 and 1' 109 | return False 110 | else: 111 | return True 112 | 113 | # 检查move的over write标志 114 | def check_move_over_write(self, to_over_write): 115 | if to_over_write != 1 and to_over_write != 0: 116 | self._err_tips = 'to_over_write only support 0 and 1' 117 | return False 118 | else: 119 | return True 120 | 121 | # 检查文件的authority属性 122 | # 合法的取值只有eInvalid, eWRPrivate, eWPrivateRPublic和空值 123 | def check_file_authority(self, authority): 124 | if authority != u'' and \ 125 | authority != u'eInvalid' and \ 126 | authority != u'eWRPrivate' and \ 127 | authority != u'eWPrivateRPublic': 128 | self._err_tips = 'file authority valid value is:' 129 | 'eInvalid, eWRPrivate, eWPrivateRPublic' 130 | return False 131 | else: 132 | return True 133 | 134 | # 检查x_cos_meta_dict, key和value都必须是UTF8编码 135 | def check_x_cos_meta_dict(self, x_cos_meta_dict): 136 | prefix_len = len('x-cos-meta-') 137 | for key in x_cos_meta_dict.keys(): 138 | if not self.check_param_unicode('x-cos-meta-key', key): 139 | return False 140 | if not self.check_param_unicode('x-cos-meta-value', 141 | x_cos_meta_dict[key]): 142 | return False 143 | if key[0:prefix_len] != u'x-cos-meta-': 144 | self._err_tips = 'x-cos-meta key must start with x-cos-meta-' 145 | return False 146 | if len(key) == prefix_len: 147 | self._err_tips = 'x-cos-meta key must not just be x-cos-meta-' 148 | return False 149 | if (len(x_cos_meta_dict[key]) == 0): 150 | self._err_tips = 'x-cos-meta value must not be empty' 151 | return False 152 | return True 153 | 154 | # 检查更新文件的flag 155 | def check_update_flag(self, flag): 156 | if flag == 0: 157 | self._err_tips = 'no any attribute to be updated!' 158 | return False 159 | else: 160 | return True 161 | 162 | # 检查list folder的order 163 | # 合法取值0(正序), 1(逆序) 164 | def check_list_order(self, list_order): 165 | if list_order != 0 and list_order != 1: 166 | self._err_tips = 'list order is invalid, please use 0(positive) or 1(reverse)!' 167 | return False 168 | else: 169 | return True 170 | 171 | # 检查list folder的pattern 172 | # 合法取值eListBoth, eListDirOnly, eListFileOnly 173 | def check_list_pattern(self, list_pattern): 174 | if list_pattern != u'eListBoth' and \ 175 | list_pattern != u'eListDirOnly' and \ 176 | list_pattern != u'eListFileOnly': 177 | self._err_tips = 'list pattern is invalid,' 178 | ' please use eListBoth or eListDirOnly or eListFileOnly' 179 | return False 180 | else: 181 | return True 182 | -------------------------------------------------------------------------------- /qcloud_cos/cos_request.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | """the request type in tencent qcloud cos""" 5 | 6 | from cos_params_check import ParamCheck 7 | import collections 8 | 9 | ################################################################################ 10 | # BaseRequest基本类型的请求 # 11 | ################################################################################ 12 | class BaseRequest(object): 13 | # bucket_name: bucket的名称 14 | # cos_path: cos的绝对路径, 即从bucket下的根/开始 15 | def __init__(self, bucket_name, cos_path): 16 | self._bucket_name = bucket_name.strip() 17 | self._cos_path = cos_path.strip() 18 | self._param_check = ParamCheck() 19 | 20 | # 设置bucket_name 21 | def set_bucket_name(self, bucket_name=u''): 22 | self._bucket_name = bucket_name.strip() 23 | 24 | # 获取bucket_name 25 | def get_bucket_name(self): 26 | return self._bucket_name 27 | 28 | # 设置cos_path 29 | def set_cos_path(self, cos_path=u''): 30 | self._cos_path = cos_path.strip() 31 | 32 | # 获取cos_path 33 | def get_cos_path(self): 34 | return self._cos_path 35 | 36 | # 获取错误信息 37 | def get_err_tips(self): 38 | return self._param_check.get_err_tips() 39 | 40 | # 检查参数是否合法 41 | def check_params_valid(self): 42 | if not self._param_check.check_param_unicode('bucket', self._bucket_name): 43 | return False 44 | return self._param_check.check_param_unicode('cos_path', self._cos_path) 45 | 46 | ################################################################################ 47 | # CreateFolderRequest 创建目录类型的请求 # 48 | ################################################################################ 49 | class CreateFolderRequest(BaseRequest): 50 | # bucket_name: bucket的名称 51 | # cos_path: cos的绝对路径, 从bucket根/开始 52 | # biz_attr: 目录的属性 53 | def __init__(self, bucket_name, cos_path, biz_attr=u''): 54 | super(CreateFolderRequest, self).__init__(bucket_name, cos_path) 55 | self._biz_attr = biz_attr 56 | 57 | # 设置biz_attr 58 | def set_biz_attr(self, biz_attr): 59 | self._biz_attr = biz_attr 60 | 61 | # 获取biz_attr 62 | def get_biz_attr(self): 63 | return self._biz_attr 64 | 65 | # 检查参数是否合法 66 | def check_params_valid(self): 67 | if not super(CreateFolderRequest, self).check_params_valid(): 68 | return False 69 | if not self._param_check.check_param_unicode('biz_attr', 70 | self._biz_attr): 71 | return False 72 | if not self._param_check.check_cos_path_valid(self._cos_path, 73 | is_file_path=False): 74 | return False 75 | return self._param_check.check_not_cos_root(self._cos_path) 76 | 77 | ################################################################################ 78 | # UploadFileRequest 单文件上传请求 # 79 | ################################################################################ 80 | class UploadFileRequest(BaseRequest): 81 | # bucket_name: bucket的名称 82 | # cos_path: cos的绝对路径(目的路径), 从bucket根/开始 83 | # local_path: 上传的本地文件路径(源路径) 84 | # biz_attr: 文件的属性 85 | # insert_only: 是否覆盖写, 0覆盖, 1不覆盖,返回错误 86 | 87 | def __init__(self, bucket_name, cos_path, local_path, biz_attr=u'', 88 | insert_only=1): 89 | super(UploadFileRequest, self).__init__(bucket_name, cos_path) 90 | self._local_path = local_path.strip() 91 | self._biz_attr = biz_attr 92 | self._insert_only = insert_only 93 | 94 | # 设置local_path 95 | def set_local_path(self, local_path): 96 | self._local_path = local_path.strip() 97 | 98 | # 获取local_path 99 | def get_local_path(self): 100 | return self._local_path 101 | 102 | # 设置biz_attr 103 | def set_biz_attr(self, biz_attr): 104 | self._biz_attr = biz_attr 105 | 106 | # 获取biz_attr 107 | def get_biz_attr(self): 108 | return self._biz_attr 109 | 110 | # 设置insert_only,0表示如果文件存在, 则覆盖 111 | def set_insert_only(self, insert_only): 112 | self._insert_only = insert_only 113 | 114 | # 获取insert_only 115 | def get_insert_only(self): 116 | return self._insert_only 117 | 118 | # 检查参数是否有效 119 | def check_params_valid(self): 120 | if not super(UploadFileRequest, self).check_params_valid(): 121 | return False 122 | if not self._param_check.check_cos_path_valid(self._cos_path, 123 | is_file_path=True): 124 | return False 125 | if not self._param_check.check_param_unicode('biz_attr', 126 | self._biz_attr): 127 | return False 128 | if not self._param_check.check_param_unicode('local_path', 129 | self._local_path): 130 | return False 131 | if not self._param_check.check_local_file_valid(self._local_path): 132 | return False 133 | if not self._param_check.check_param_int('insert_only', 134 | self._insert_only): 135 | return False 136 | return self._param_check.check_insert_only(self._insert_only) 137 | 138 | ################################################################################ 139 | # UploadSliceFileRequest 分片文件上传请求 # 140 | ################################################################################ 141 | class UploadSliceFileRequest(UploadFileRequest): 142 | # bucket_name: bucket的名称 143 | # cos_path: cos的绝对路径(目的路径), 从bucket根/开始 144 | # local_path: 上传的本地文件路径(源路径) 145 | # biz_attr: 文件的属性 146 | # slice_size: 分片大小(字节, 默认1MB) 147 | def __init__(self, bucket_name, cos_path, local_path, slice_size=1024*1024, 148 | biz_attr=u''): 149 | super(UploadSliceFileRequest, self).__init__(bucket_name, cos_path, local_path, 150 | biz_attr) 151 | self._slice_size = slice_size 152 | 153 | # 设置分片大小 154 | def set_slice_size(self, slice_size): 155 | self._slice_size = slice_size 156 | 157 | # 获取分片大小 158 | def get_slice_size(self): 159 | return self._slice_size 160 | 161 | # 检查参数是否有效 162 | def check_params_valid(self): 163 | if not super(UploadSliceFileRequest, self).check_params_valid(): 164 | return False 165 | return self._param_check.check_slice_size(self._slice_size) 166 | 167 | ################################################################################ 168 | # UpdateFolderRequest 更新目录请求 # 169 | ################################################################################ 170 | class UpdateFolderRequest(BaseRequest): 171 | # biz_attr: 目录属性 172 | def __init__(self, bucket_name, cos_path, biz_attr=u''): 173 | super(UpdateFolderRequest, self).__init__(bucket_name, cos_path) 174 | self._biz_attr = biz_attr 175 | 176 | # 设置biz_attr 177 | def set_biz_attr(self, biz_attr): 178 | self._biz_attr = biz_attr 179 | 180 | # 获取biz_attr 181 | def get_biz_attr(self): 182 | return self._biz_attr 183 | 184 | # 检查参数是否有效 185 | def check_params_valid(self): 186 | if not super(UpdateFolderRequest, self).check_params_valid(): 187 | return False 188 | if not self._param_check.check_cos_path_valid(self._cos_path, 189 | is_file_path=False): 190 | return False 191 | if not self._param_check.check_not_cos_root(self._cos_path): 192 | return False 193 | return self._param_check.check_param_unicode('biz_attr', 194 | self._biz_attr) 195 | 196 | ################################################################################ 197 | # UpdateFileRequest 更新文件请求 # 198 | ################################################################################ 199 | class UpdateFileRequest(BaseRequest): 200 | # bucket_name: bucket的名称 201 | # cos_path: cos的绝对路径, 从bucket根/开始 202 | # biz_attr: 要更新的文件的属性 203 | # authority: 文件权限: 204 | # eInvalid(继承bucket), 205 | # eWRPrivate(私有读写), 206 | # eWPrivateRPublic(私有写, 公有读) 207 | # customer_header: 用户自定义的HTTP请求头,包括以下成员 208 | # cache_control: 文件的缓存机制,参见HTTP的Cache-Control 209 | # content_type: 文件的MIME信息,参见HTTP的Content-Type 210 | # content_disposition: MIME协议的扩展,参见HTTP的Content-Disposition 211 | # content_language: 文件的语言, 参见HTTP的Content-Language 212 | # content_encoding: body的编码, 参见HTTP的Content-Encoding 213 | # _x_cos_meta_dict: 用户自定义的属性, key是以x-cos-meta-开头,value为属性值 214 | def __init__(self, bucket_name, cos_path): 215 | super(UpdateFileRequest, self).__init__(bucket_name, cos_path) 216 | self._flag = 0 217 | self._biz_attr = u'' 218 | self._custom_headers = {} 219 | self._authority = u'' 220 | self._cache_control = u'' 221 | self._content_type = u'' 222 | self._content_disposition = u'' 223 | self._content_language = u'' 224 | self._content_encoding = u'' 225 | self._x_cos_meta_dict = {} 226 | 227 | # 获取flag 228 | def get_flag(self): 229 | return self._flag 230 | 231 | # 设置biz_attr 232 | def set_biz_attr(self, biz_attr): 233 | self._biz_attr = biz_attr 234 | self._flag |= 0x01 235 | 236 | # 获取biz_attr 237 | def get_biz_attr(self): 238 | return self._biz_attr 239 | 240 | # 设置authority, 合法取值如下所示 241 | # eInvalid(继承bucket), 242 | # eWRPrivate(私有读写), 243 | # eWPrivateRPublic(私有写, 公有读) 244 | def set_authority(self, authority): 245 | self._authority = authority 246 | self._flag |= 0x80 247 | 248 | # 获取authority 249 | def get_authority(self): 250 | return self._authority 251 | 252 | # 设置缓存机制Cache-Control 253 | def set_cache_control(self, cache_control): 254 | self._cache_control = cache_control 255 | self._flag |= 0x40 256 | self._custom_headers[u'Cache-Control'] = cache_control 257 | 258 | # 设置Content-Type 259 | def set_content_type(self, content_type): 260 | self._content_type = content_type 261 | self._flag |= 0x40 262 | self._custom_headers['Content-Type'] = content_type 263 | 264 | # 设置Content-Disposition 265 | def set_content_disposition(self, content_disposition): 266 | self._content_disposition = content_disposition 267 | self._flag |= 0x40 268 | self._custom_headers['Content-Disposition'] = content_disposition 269 | 270 | # 设置Content-Language 271 | def set_content_language(self, content_language): 272 | self._content_language = content_language 273 | self._flag |= 0x40 274 | self._custom_headers['Content-Language'] = content_language 275 | 276 | # 设置Content-Encoding 277 | def set_content_encoding(self, content_encoding): 278 | self._content_encoding = content_encoding 279 | self._flag |= 0x40 280 | self._custom_headers['Content-Encoding'] = content_encoding 281 | 282 | # 设置自定义的x-cos-meta, key以x-cos-meta-开头 283 | # 例如自定义key为u'x-cos-meta-len', value为u'1024' 284 | def set_x_cos_meta(self, key, value): 285 | self._flag |= 0x40 286 | self._x_cos_meta_dict[key] = value 287 | self._custom_headers[key] = value 288 | 289 | # convert a dict's keys & values from `unicode` to `str` 290 | def _convert_dict(self, data): 291 | if isinstance(data, basestring): 292 | return str(data) 293 | elif isinstance(data, collections.Mapping): 294 | return dict(map(self._convert_dict, data.iteritems())) 295 | elif isinstance(data, collections.Iterable): 296 | return type(data)(map(self._convert_dict, data)) 297 | else: 298 | return data 299 | 300 | # 获取自定义的HTTP头 301 | def get_custom_headers(self): 302 | return self._convert_dict(self._custom_headers) 303 | 304 | # 检查参数是否合法 305 | def check_params_valid(self): 306 | if not super(UpdateFileRequest, self).check_params_valid(): 307 | return False 308 | if not self._param_check.check_cos_path_valid(self._cos_path, 309 | is_file_path=True): 310 | return False 311 | if not self._param_check.check_update_flag(self._flag): 312 | return False 313 | if not self._param_check.check_param_unicode('biz_attr', 314 | self._biz_attr): 315 | return False 316 | if not self._param_check.check_param_unicode('authority', 317 | self._authority): 318 | return False 319 | if not self._param_check.check_file_authority(self._authority): 320 | return False 321 | if not self._param_check.check_param_unicode('cache_control', 322 | self._cache_control): 323 | return False 324 | if not self._param_check.check_param_unicode('content_type', 325 | self._content_type): 326 | return False 327 | if not self._param_check.check_param_unicode('content_disposition', 328 | self._content_disposition): 329 | return False 330 | if not self._param_check.check_param_unicode('content_language', 331 | self._content_language): 332 | return False 333 | if not self._param_check.check_param_unicode('content_encoding', 334 | self._content_encoding): 335 | return False 336 | return self._param_check.check_x_cos_meta_dict(self._x_cos_meta_dict) 337 | 338 | ################################################################################ 339 | # MoveFileRequest 移动文件请求 # 340 | ################################################################################ 341 | class MoveFileRequest(BaseRequest): 342 | # bucket_name: bucket的名称 343 | # src_cos_path: 要移动的文件源路径 344 | # dst_cos_path: 要移动到文件目的地路径 345 | # to_over_write: 是否覆盖, 0(默认): 不覆盖, 1: 覆盖 346 | def __init__(self, bucket_name, src_cos_path, dst_cos_path, to_over_write = 0): 347 | super(MoveFileRequest, self).__init__(bucket_name, src_cos_path) 348 | self._src_cos_path = src_cos_path 349 | self._dst_cos_path = dst_cos_path 350 | self._to_over_write = to_over_write 351 | 352 | # 设置src_cos_path 353 | def set_src_cos_path(self, src_cos_path): 354 | self._src_cos_path = src_cos_path 355 | 356 | # 获取src cos path 357 | def get_src_cos_path(self): 358 | return self._src_cos_path 359 | 360 | # 设置dst_cos_path 361 | def set_src_cos_path(self, dst_cos_path): 362 | self._dst_cos_path = dst_cos_path 363 | 364 | # 获取dst_cos_path 365 | def get_dst_cos_path(self): 366 | return self._dst_cos_path 367 | 368 | # 设置over_write标志 369 | def set_over_write(self, to_over_write): 370 | self._to_over_write = to_over_write 371 | 372 | # 获取over_write标志 373 | def get_over_write(self): 374 | return self._to_over_write 375 | 376 | # 检查参数是否合法 377 | def check_params_valid(self): 378 | if not super(MoveFileRequest, self).check_params_valid(): 379 | return False 380 | if not self._param_check.check_cos_path_valid(self._src_cos_path, 381 | is_file_path=True): 382 | return False 383 | if not self._param_check.check_param_unicode('dst_cos_path', 384 | self._dst_cos_path): 385 | return False 386 | if not self._param_check.check_cos_path_valid(self._dst_cos_path, 387 | is_file_path=True): 388 | return False 389 | if not self._param_check.check_param_int('over_write', 390 | self._to_over_write): 391 | return False 392 | return self._param_check.check_move_over_write(self._to_over_write) 393 | 394 | ################################################################################ 395 | # StatRequest 获取文件属性请求 # 396 | ################################################################################ 397 | class StatFileRequest(BaseRequest): 398 | # bucket_name: bucket的名称 399 | # cos_path: cos的文件路径, 从bucket根/开始, 不以/结束 400 | def __init__(self, bucket_name, cos_path): 401 | super(StatFileRequest, self).__init__(bucket_name, cos_path) 402 | 403 | # 检查参数是否合法 404 | def check_params_valid(self): 405 | if not super(StatFileRequest, self).check_params_valid(): 406 | return False 407 | return self._param_check.check_cos_path_valid(self._cos_path, 408 | is_file_path=True) 409 | 410 | ################################################################################ 411 | # StatRequest 获取目录属性请求 # 412 | ################################################################################ 413 | class StatFolderRequest(BaseRequest): 414 | # bucket_name: bucket的名称 415 | # cos_path: cos的目录路径, 从bucket根/开始, 以/结束 416 | def __init__(self, bucket_name, cos_path): 417 | super(StatFolderRequest, self).__init__(bucket_name, cos_path) 418 | 419 | # 检查参数是否合法 420 | def check_params_valid(self): 421 | if not super(StatFolderRequest, self).check_params_valid(): 422 | return False 423 | return self._param_check.check_cos_path_valid(self._cos_path, 424 | is_file_path=False) 425 | 426 | ################################################################################ 427 | # DelFileRequest 删除文件请求 # 428 | ################################################################################ 429 | class DelFileRequest(BaseRequest): 430 | # bucket_name: bucket的名称 431 | # cos_path: cos的文件路径, 从bucket根/开始, 不以/结束 432 | def __init__(self, bucket_name, cos_path): 433 | super(DelFileRequest, self).__init__(bucket_name, cos_path) 434 | 435 | # 检查参数是否合法 436 | def check_params_valid(self): 437 | if not super(DelFileRequest, self).check_params_valid(): 438 | return False 439 | return self._param_check.check_cos_path_valid(self._cos_path, 440 | is_file_path=True) 441 | 442 | ################################################################################ 443 | # DelFolderRequest 删除目录请求 # 444 | ################################################################################ 445 | class DelFolderRequest(BaseRequest): 446 | # bucket_name: bucket的名称 447 | # cos_path: cos的目录路径, 从bucket根/开始, 以/结束 448 | def __init__(self, bucket_name, cos_path): 449 | super(DelFolderRequest, self).__init__(bucket_name, cos_path) 450 | 451 | # 检查参数合法 452 | def check_params_valid(self): 453 | if not super(DelFolderRequest, self).check_params_valid(): 454 | return False 455 | if not self._param_check.check_cos_path_valid(self._cos_path, 456 | is_file_path=False): 457 | return False 458 | return self._param_check.check_not_cos_root(self._cos_path) 459 | 460 | ################################################################################ 461 | # ListFolderRequest 获取目录列表的请求 # 462 | ################################################################################ 463 | class ListFolderRequest(BaseRequest): 464 | # bucket_name: bucket的名称 465 | # cos_path: cos的绝对路径, 从bucket根/开始 466 | # num: 搜索数量 467 | # pattern: 搜索的类型, 合法取值eListBoth, eListDirOnly, eListFileOnly 468 | # prefix: 搜索前缀 469 | # context: 搜索上下文 470 | # order: 搜索顺序(0 正序, 1逆序) 471 | def __init__(self, bucket_name, cos_path, num=199, 472 | pattern = u'eListBoth', prefix=u'', context=u'', order=0): 473 | super(ListFolderRequest, self).__init__(bucket_name, cos_path) 474 | self._num = num 475 | self._pattern = pattern 476 | self._prefix = prefix 477 | self._context = context 478 | self._order = order 479 | 480 | # 设置List数量 481 | def set_num(self, num): 482 | self._num = num 483 | 484 | # 获取List数量 485 | def get_num(self): 486 | return self._num 487 | 488 | # 获取List类型 489 | def set_pattern(self, pattern): 490 | self._pattern = pattern 491 | 492 | # 获取List类型 493 | def get_pattern(self): 494 | return self._pattern 495 | 496 | # 设置前缀 497 | def set_prefix(self, prefix): 498 | self._preifx = prefix 499 | 500 | # 获取前缀 501 | def get_prefix(self): 502 | return self._prefix 503 | 504 | # 设置搜索上下文 505 | def set_context(self, context): 506 | self._context = context 507 | 508 | # 获取搜索上下文 509 | def get_context(self): 510 | return self._context 511 | 512 | # 设置搜索顺序 513 | def set_order(self, order=0): 514 | self._order = order 515 | 516 | # 获取搜索顺序 517 | def get_order(self): 518 | return self._order 519 | 520 | # 检查参数是否有效 521 | def check_params_valid(self): 522 | if not super(ListFolderRequest, self).check_params_valid(): 523 | return False 524 | if not self._param_check.check_cos_path_valid(self._cos_path, 525 | is_file_path=False): 526 | return False 527 | if not self._param_check.check_param_unicode('prefix', self._prefix): 528 | return False 529 | if not self._param_check.check_param_unicode('context', self._context): 530 | return False 531 | if not self._param_check.check_list_order(self._order): 532 | return False 533 | return self._param_check.check_list_pattern(self._pattern) 534 | -------------------------------------------------------------------------------- /sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | from qcloud_cos import CosClient 5 | from qcloud_cos import UploadFileRequest 6 | from qcloud_cos import UploadSliceFileRequest 7 | from qcloud_cos import UpdateFileRequest 8 | from qcloud_cos import UpdateFolderRequest 9 | from qcloud_cos import DelFileRequest 10 | from qcloud_cos import MoveFileRequest 11 | from qcloud_cos import DelFolderRequest 12 | from qcloud_cos import CreateFolderRequest 13 | from qcloud_cos import StatFileRequest 14 | from qcloud_cos import StatFolderRequest 15 | from qcloud_cos import ListFolderRequest 16 | 17 | def cos_demo(): 18 | # 设置用户属性, 包括appid, secret_id和secret_key 19 | # 这些属性可以在cos控制台获取(https://console.qcloud.com/cos) 20 | appid = 100000 # 替换为用户的appid 21 | secret_id = u'xxxxxxxx' # 替换为用户的secret_id 22 | secret_key = u'xxxxxxx' # 替换为用户的secret_key 23 | cos_client = CosClient(appid, secret_id, secret_key) 24 | 25 | # 设置要操作的bucket 26 | bucket = u'mybucket' 27 | 28 | ############################################################################ 29 | # 文件操作 # 30 | ############################################################################ 31 | # 1. 上传文件(默认不覆盖) 32 | # 将本地的local_file_1.txt上传到bucket的根分区下,并命名为sample_file.txt 33 | # 默认不覆盖, 如果cos上文件存在,则会返回错误 34 | request = UploadFileRequest(bucket, u'/sample_file.txt', u'local_file_1.txt') 35 | upload_file_ret = cos_client.upload_file(request) 36 | print 'upload file ret:', repr(upload_file_ret) 37 | 38 | # 2. 上传文件(覆盖文件) 39 | # 将本地的local_file_2.txt上传到bucket的根分区下,覆盖已上传的sample_file.txt 40 | request = UploadFileRequest(bucket, u'/sample_file.txt', u'local_file_2.txt') 41 | request.set_insert_only(0) # 设置允许覆盖 42 | upload_file_ret = cos_client.upload_file(request) 43 | print 'overwrite file ret:', repr(upload_file_ret) 44 | 45 | # 3. 获取文件属性 46 | request = StatFileRequest(bucket, u'/sample_file.txt') 47 | stat_file_ret = cos_client.stat_file(request) 48 | print 'stat file ret:', repr(stat_file_ret) 49 | 50 | # 4. 更新文件属性 51 | request = UpdateFileRequest(bucket, u'/sample_file.txt') 52 | 53 | request.set_biz_attr(u'这是个demo文件') # 设置文件biz_attr属性 54 | request.set_authority(u'eWRPrivate') # 设置文件的权限 55 | request.set_cache_control(u'cache_xxx') # 设置Cache-Control 56 | request.set_content_type(u'application/text') # 设置Content-Type 57 | request.set_content_disposition(u'ccccxxx.txt') # 设置Content-Disposition 58 | request.set_content_language(u'english') # 设置Content-Language 59 | request.set_x_cos_meta(u'x-cos-meta-xxx', u'xxx') # 设置自定义的x-cos-meta-属性 60 | request.set_x_cos_meta(u'x-cos-meta-yyy', u'yyy') # 设置自定义的x-cos-meta-属性 61 | 62 | update_file_ret = cos_client.update_file(request) 63 | print 'update file ret:', repr(update_file_ret) 64 | 65 | # 5. 更新后再次获取文件属性 66 | request = StatFileRequest(bucket, u'/sample_file.txt') 67 | stat_file_ret = cos_client.stat_file(request) 68 | print 'stat file ret:', repr(stat_file_ret) 69 | 70 | # 6. 移动文件, 将sample_file.txt移动位sample_file_move.txt 71 | request = MoveFileRequest(bucket, u'/sample_file.txt', u'/sample_file_move.txt') 72 | stat_file_ret = cos_client.move_file(request) 73 | print 'move file ret:', repr(stat_file_ret) 74 | 75 | # 7. 删除文件 76 | request = DelFileRequest(bucket, u'/sample_file_move.txt') 77 | del_ret = cos_client.del_file(request) 78 | print 'del file ret:', repr(del_ret) 79 | 80 | ############################################################################ 81 | # 目录操作 # 82 | ############################################################################ 83 | # 1. 生成目录, 目录名为sample_folder 84 | request = CreateFolderRequest(bucket, u'/sample_folder/') 85 | create_folder_ret = cos_client.create_folder(request) 86 | print 'create folder ret:', create_folder_ret 87 | 88 | # 2. 更新目录的biz_attr属性 89 | request = UpdateFolderRequest(bucket, u'/sample_folder/', u'这是一个测试目录') 90 | update_folder_ret = cos_client.update_folder(request) 91 | print 'update folder ret:', repr(update_folder_ret) 92 | 93 | # 3. 获取目录属性 94 | request = StatFolderRequest(bucket, u'/sample_folder/') 95 | stat_folder_ret = cos_client.stat_folder(request) 96 | print 'stat folder ret:', repr(stat_folder_ret) 97 | 98 | # 4. list目录, 获取目录下的成员 99 | request = ListFolderRequest(bucket, u'/sample_folder/') 100 | list_folder_ret = cos_client.list_folder(request) 101 | print 'list folder ret:', repr(list_folder_ret) 102 | 103 | # 5. 删除目录 104 | request = DelFolderRequest(bucket, u'/sample_folder/') 105 | delete_folder_ret = cos_client.del_folder(request) 106 | print 'delete folder ret:', repr(delete_folder_ret) 107 | 108 | if __name__ == '__main__': 109 | cos_demo() 110 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | import sys 5 | if sys.version_info[0] != 2 or sys.version_info[1] < 6: 6 | sys.exit('Sorry, only python 2.6 or 2.7 is supported') 7 | 8 | from setuptools import setup, find_packages 9 | setup( 10 | name = 'qcloud_cos', 11 | version = '3.3', 12 | description = 'python sdk for tencent qcloud cos', 13 | license = 'MIT License', 14 | install_requires=['requests'], 15 | 16 | author = 'chengwu', 17 | author_email = 'chengwu@tencent.com', 18 | 19 | packages = find_packages(), 20 | ) 21 | --------------------------------------------------------------------------------