multibucket 0.1.2__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
multibucket/__init__.py
ADDED
|
Binary file
|
multibucket/ucloud.pyi
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from typing import BinaryIO, Callable
|
|
4
|
+
|
|
5
|
+
class UCloudFileClient:
|
|
6
|
+
"""
|
|
7
|
+
UCloud UFile 文件管理客户端
|
|
8
|
+
|
|
9
|
+
参考文档:
|
|
10
|
+
- 签名算法: https://docs.ucloud.cn/ufile/api/authorization
|
|
11
|
+
- 上传文件: https://docs.ucloud.cn/api/ufile-api/put_file
|
|
12
|
+
- 删除文件: https://docs.ucloud.cn/api/ufile-api/delete_file
|
|
13
|
+
- 查询文件: https://docs.ucloud.cn/api/ufile-api/head_file
|
|
14
|
+
- 初始化分片: https://docs.ucloud.cn/api/ufile-api/initiate_multipart_upload
|
|
15
|
+
- 上传分片: https://docs.ucloud.cn/api/ufile-api/upload_part
|
|
16
|
+
- 完成分片: https://docs.ucloud.cn/api/ufile-api/finish_multipart_upload
|
|
17
|
+
- 放弃分片: https://docs.ucloud.cn/api/ufile-api/abort_multipart_upload
|
|
18
|
+
"""
|
|
19
|
+
public_key: Incomplete
|
|
20
|
+
private_key: Incomplete
|
|
21
|
+
bucket: Incomplete
|
|
22
|
+
region: Incomplete
|
|
23
|
+
internal: Incomplete
|
|
24
|
+
external_url: Incomplete
|
|
25
|
+
intranet_url: Incomplete
|
|
26
|
+
host: Incomplete
|
|
27
|
+
domain_url: Incomplete
|
|
28
|
+
def __init__(self, public_key: str, private_key: str, bucket: str, region: str = 'cn-bj', internal: bool = False, domain_url: str | None = None) -> None:
|
|
29
|
+
"""
|
|
30
|
+
:param public_key: UCloud 公钥
|
|
31
|
+
:param private_key: UCloud 私钥
|
|
32
|
+
:param bucket: 存储空间名称
|
|
33
|
+
:param region: 地域标识,如 cn-bj、cn-sh2、cn-wlcb 等
|
|
34
|
+
:param internal: 是否使用内网访问,默认 False(外网)
|
|
35
|
+
内网域名格式: {bucket}.internal-{region}.ufileos.com
|
|
36
|
+
外网域名格式: {bucket}.{region}.ufileos.com
|
|
37
|
+
:param domain_url: 自定义域名 URL,如 CDN 域名。若不指定则使用默认的 host
|
|
38
|
+
"""
|
|
39
|
+
def key_sort(self, params: dict) -> dict:
|
|
40
|
+
"""
|
|
41
|
+
对字典按 key 排序
|
|
42
|
+
"""
|
|
43
|
+
def canonicalized_resource(self, key: str, query_params: dict[str, str] | None = None) -> str:
|
|
44
|
+
"""
|
|
45
|
+
构造规范化资源路径
|
|
46
|
+
|
|
47
|
+
:param key: 文件 key
|
|
48
|
+
:param query_params: 查询参数字典,会拼接到资源路径后用于签名
|
|
49
|
+
"""
|
|
50
|
+
def canonicalized_ucloud_headers(self, headers: dict[str, str]) -> str:
|
|
51
|
+
"""
|
|
52
|
+
只取 X-UCloud 开头的 header
|
|
53
|
+
"""
|
|
54
|
+
def sign(self, string_to_sign: str) -> str:
|
|
55
|
+
"""
|
|
56
|
+
HMAC-SHA1 + Base64
|
|
57
|
+
"""
|
|
58
|
+
def make_authorization(self, key: str, expires: str, method: str = 'GET', content_md5: str = '', content_type: str = '', headers: dict[str, str] | None = None, query_params: dict[str, str] | None = None) -> str:
|
|
59
|
+
'''
|
|
60
|
+
生成签名
|
|
61
|
+
|
|
62
|
+
StringToSign = HTTP-Verb + "
|
|
63
|
+
" +
|
|
64
|
+
Content-MD5 + "
|
|
65
|
+
" +
|
|
66
|
+
Content-Type + "
|
|
67
|
+
" +
|
|
68
|
+
Date + "
|
|
69
|
+
" +
|
|
70
|
+
CanonicalizedUCloudHeaders +
|
|
71
|
+
CanonicalizedResource
|
|
72
|
+
|
|
73
|
+
:param key: 文件 key
|
|
74
|
+
:param expires: 时间戳(秒)
|
|
75
|
+
:param method: HTTP 方法
|
|
76
|
+
:param content_md5: Content-MD5 值
|
|
77
|
+
:param content_type: Content-Type 值
|
|
78
|
+
:param headers: 请求头字典
|
|
79
|
+
:param query_params: 查询参数字典,参与签名
|
|
80
|
+
:return: Base64 编码的签名字符串
|
|
81
|
+
'''
|
|
82
|
+
def get_path_url(self, key: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
获取文件的完整访问 URL(使用自定义域名或默认域名)
|
|
85
|
+
|
|
86
|
+
:param key: 文件在 UFile 中的 key
|
|
87
|
+
:return: 完整的文件访问 URL
|
|
88
|
+
"""
|
|
89
|
+
def get_shared_url(self, key: str, expire_time: int = 1200) -> str:
|
|
90
|
+
"""
|
|
91
|
+
生成带签名的分享 URL(查询字符串身份验证)
|
|
92
|
+
|
|
93
|
+
适用于分享私有空间文件给第三方访问,无需代理请求。
|
|
94
|
+
默认使用外网地址,除非显式指定了自定义域名。
|
|
95
|
+
参考文档: https://docs.ucloud.cn/ufile/api/authorization?id=查询字符串请求身份验证替代项
|
|
96
|
+
|
|
97
|
+
:param key: 文件在 UFile 中的 key
|
|
98
|
+
:param expire_time: 过期时间(秒),默认 1200 秒(20 分钟)
|
|
99
|
+
:return: 带签名的完整访问 URL
|
|
100
|
+
"""
|
|
101
|
+
def put(self, key: str, data: bytes | str | BinaryIO, content_type: str | None = None, content_md5: str | None = None, headers: dict[str, str] | None = None, timeout: float | tuple | None = None) -> requests.Response:
|
|
102
|
+
"""
|
|
103
|
+
上传文件 (PutFile)
|
|
104
|
+
|
|
105
|
+
PUT /<object_name>
|
|
106
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
107
|
+
|
|
108
|
+
:param key: 文件在 UFile 中的 key(object_name)
|
|
109
|
+
:param data: 文件内容(bytes / str / 文件对象)
|
|
110
|
+
:param content_type: MIME 类型,如 image/jpg、application/octet-stream
|
|
111
|
+
:param content_md5: 文件内容的 MD5 摘要,用于完整性校验
|
|
112
|
+
:param headers: 额外的请求头(如 X-Ufile-Storage-Class、X-Ufile-Meta-* 等)
|
|
113
|
+
:param timeout: 超时时间(秒),可以是 float 或 (connect_timeout, read_timeout) 元组
|
|
114
|
+
:return: requests.Response(成功返回 200,无 body)
|
|
115
|
+
"""
|
|
116
|
+
def put_with_retry(self, key: str, data: bytes | str | BinaryIO, content_type: str | None = None, content_md5: str | None = None, headers: dict[str, str] | None = None, timeout: float | tuple | None = None, retries: int = 3) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
带重试机制的上传文件
|
|
119
|
+
|
|
120
|
+
通过 put 方法上传,状态码为 200 表示上传成功。
|
|
121
|
+
如果上传失败会自动重试,直到达到指定重试次数。
|
|
122
|
+
|
|
123
|
+
注意: 如果 data 是文件对象,会自动读取到内存以支持重试。
|
|
124
|
+
对于大文件,建议直接使用 put 方法或分片上传。
|
|
125
|
+
|
|
126
|
+
:param key: 文件在 UFile 中的 key(object_name)
|
|
127
|
+
:param data: 文件内容(bytes / str / 文件对象)
|
|
128
|
+
:param content_type: MIME 类型,如 image/jpg、application/octet-stream
|
|
129
|
+
:param content_md5: 文件内容的 MD5 摘要,用于完整性校验
|
|
130
|
+
:param headers: 额外的请求头(如 X-Ufile-Storage-Class、X-Ufile-Meta-* 等)
|
|
131
|
+
:param timeout: 超时时间(秒),可以是 float 或 (connect_timeout, read_timeout) 元组
|
|
132
|
+
:param retries: 最大重试次数,默认 3 次
|
|
133
|
+
:return: True 表示上传成功,False 表示上传失败
|
|
134
|
+
"""
|
|
135
|
+
def get(self, key: str, headers: dict[str, str] | None = None) -> requests.Response:
|
|
136
|
+
"""
|
|
137
|
+
下载文件 (GetFile)
|
|
138
|
+
|
|
139
|
+
GET /<object_name>
|
|
140
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
141
|
+
|
|
142
|
+
:param key: 文件在 UFile 中的 key
|
|
143
|
+
:param headers: 额外的请求头
|
|
144
|
+
:return: requests.Response(response.content 为文件内容)
|
|
145
|
+
"""
|
|
146
|
+
def head(self, key: str, headers: dict[str, str] | None = None) -> requests.Response:
|
|
147
|
+
"""
|
|
148
|
+
查询文件基本信息 (HeadFile)
|
|
149
|
+
|
|
150
|
+
HEAD /<object_name>
|
|
151
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
152
|
+
|
|
153
|
+
响应头包含:
|
|
154
|
+
- Content-Type: 文件类型
|
|
155
|
+
- Content-Length: 文件长度
|
|
156
|
+
- ETag: 文件在 US3 的哈希值
|
|
157
|
+
- Last-Modified: 最后修改日期
|
|
158
|
+
- X-Ufile-Meta-*: 用户自定义元数据
|
|
159
|
+
- X-Ufile-Storage-Class: 存储类型
|
|
160
|
+
|
|
161
|
+
:param key: 文件在 UFile 中的 key
|
|
162
|
+
:param headers: 额外的请求头
|
|
163
|
+
:return: requests.Response(无 body,信息在 response.headers 中)
|
|
164
|
+
"""
|
|
165
|
+
def exists(self, key: str) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
判断文件是否存在
|
|
168
|
+
|
|
169
|
+
:param key: 文件在 UFile 中的 key
|
|
170
|
+
:return: True 表示文件存在,False 表示不存在
|
|
171
|
+
"""
|
|
172
|
+
def delete(self, key: str, headers: dict[str, str] | None = None) -> requests.Response:
|
|
173
|
+
"""
|
|
174
|
+
删除文件 (DeleteFile)
|
|
175
|
+
|
|
176
|
+
DELETE /<object_name>
|
|
177
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
178
|
+
|
|
179
|
+
:param key: 文件在 UFile 中的 key
|
|
180
|
+
:param headers: 额外的请求头
|
|
181
|
+
:return: requests.Response(成功返回 204 No Content)
|
|
182
|
+
"""
|
|
183
|
+
def list(self, prefix: str = '', marker: str = '', limit: int = 20, headers: dict[str, str] | None = None) -> requests.Response:
|
|
184
|
+
"""
|
|
185
|
+
列出文件 (GetFileList)
|
|
186
|
+
|
|
187
|
+
GET /?prefix=<prefix>&marker=<marker>&limit=<limit>
|
|
188
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
189
|
+
|
|
190
|
+
:param prefix: 文件名前缀过滤
|
|
191
|
+
:param marker: 分页标记,首次传空,后续用返回值中的 NextMarker
|
|
192
|
+
:param limit: 每次返回最大条数,默认 20
|
|
193
|
+
:param headers: 额外的请求头
|
|
194
|
+
:return: requests.Response(JSON 格式,包含 DataSet / NextMarker / TotalCount)
|
|
195
|
+
"""
|
|
196
|
+
def initiate_multipart_upload(self, key: str, content_type: str | None = None, headers: dict[str, str] | None = None) -> requests.Response:
|
|
197
|
+
"""
|
|
198
|
+
初始化分片上传 (InitiateMultipartUpload)
|
|
199
|
+
|
|
200
|
+
POST /<object_name>?uploads
|
|
201
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
202
|
+
|
|
203
|
+
:param key: 文件在 UFile 中的 key
|
|
204
|
+
:param content_type: MIME 类型
|
|
205
|
+
:param headers: 额外的请求头(如 X-Ufile-Storage-Class、X-Ufile-Meta-* 等)
|
|
206
|
+
:return: requests.Response(JSON 包含 UploadId / BlkSize / Bucket / Key)
|
|
207
|
+
"""
|
|
208
|
+
def upload_part(self, key: str, upload_id: str, part_number: int, data: bytes, content_type: str = 'application/octet-stream', headers: dict[str, str] | None = None) -> requests.Response:
|
|
209
|
+
"""
|
|
210
|
+
上传分片 (UploadPart)
|
|
211
|
+
|
|
212
|
+
PUT /<object_name>?uploadId=<upload_id>&partNumber=<part_number>
|
|
213
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
214
|
+
|
|
215
|
+
:param key: 文件在 UFile 中的 key
|
|
216
|
+
:param upload_id: 初始化分片返回的 UploadId
|
|
217
|
+
:param part_number: 分片序号(从 0 开始)
|
|
218
|
+
:param data: 分片数据(bytes)
|
|
219
|
+
:param content_type: MIME 类型,默认 application/octet-stream
|
|
220
|
+
:param headers: 额外的请求头
|
|
221
|
+
:return: requests.Response(JSON 包含 PartNumber,响应头包含 ETag)
|
|
222
|
+
"""
|
|
223
|
+
def finish_multipart_upload(self, key: str, upload_id: str, etags: list[str], new_object: str | None = None, headers: dict[str, str] | None = None) -> requests.Response:
|
|
224
|
+
"""
|
|
225
|
+
完成分片上传 (FinishMultipartUpload)
|
|
226
|
+
|
|
227
|
+
POST /<object_name>?uploadId=<upload_id>[&newObject=<new_object>]
|
|
228
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
229
|
+
|
|
230
|
+
请求 body 为所有分片 ETag 用逗号拼接的字符串
|
|
231
|
+
|
|
232
|
+
:param key: 文件在 UFile 中的 key
|
|
233
|
+
:param upload_id: 初始化分片返回的 UploadId
|
|
234
|
+
:param etags: 各分片 ETag 列表(按 partNumber 顺序)
|
|
235
|
+
:param new_object: 可选,最终文件名,若不指定则使用原 key
|
|
236
|
+
:param headers: 额外的请求头
|
|
237
|
+
:return: requests.Response(JSON 包含 Bucket / Key / FileSize)
|
|
238
|
+
"""
|
|
239
|
+
def abort_multipart_upload(self, key: str, upload_id: str, headers: dict[str, str] | None = None) -> requests.Response:
|
|
240
|
+
"""
|
|
241
|
+
放弃分片上传 (AbortMultipartUpload)
|
|
242
|
+
|
|
243
|
+
DELETE /<object_name>?uploadId=<upload_id>
|
|
244
|
+
Host: <bucket_name>.<region>.ufileos.com
|
|
245
|
+
|
|
246
|
+
放弃后不能再使用该 upload_id,已上传的分片数据也会被删除
|
|
247
|
+
|
|
248
|
+
:param key: 文件在 UFile 中的 key
|
|
249
|
+
:param upload_id: 初始化分片返回的 UploadId
|
|
250
|
+
:param headers: 额外的请求头
|
|
251
|
+
:return: requests.Response
|
|
252
|
+
"""
|
|
253
|
+
def multipart_upload(self, key: str, file_path: str, content_type: str | None = None, part_size: int | None = None, headers: dict[str, str] | None = None, callback: Callable[[int, int, int, int], None] | None = None) -> dict:
|
|
254
|
+
"""
|
|
255
|
+
分片上传高级封装:自动分片、上传、完成
|
|
256
|
+
|
|
257
|
+
流程: 初始化分片 -> 循环上传分片 -> 完成分片
|
|
258
|
+
如果中途失败会自动调用 abort_multipart_upload 放弃上传
|
|
259
|
+
|
|
260
|
+
:param key: 文件在 UFile 中的 key
|
|
261
|
+
:param file_path: 本地文件路径
|
|
262
|
+
:param content_type: MIME 类型,默认 application/octet-stream
|
|
263
|
+
:param part_size: 分片大小(字节),默认 4MB(4 * 1024 * 1024)
|
|
264
|
+
:param headers: 额外的请求头
|
|
265
|
+
:param callback: 上传进度回调 callback(part_number, total_parts, uploaded_bytes, total_bytes)
|
|
266
|
+
:return: dict,包含 upload_id / etags / result
|
|
267
|
+
:raises: Exception 上传失败时抛出异常
|
|
268
|
+
"""
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: multibucket
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: 多对象存储桶 SDK, 支持 UCloud UFile 等
|
|
5
|
+
Home-page:
|
|
6
|
+
Author: zhenzi0322
|
|
7
|
+
Author-email: zhenzi0322 <82131529@qq.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
description-content-type: text/markdown
|
|
11
|
+
Description:
|
|
12
|
+
# Multibucket
|
|
13
|
+
|
|
14
|
+
多对象存储桶 SDK,支持 UCloud UFile 等云存储服务。
|
|
15
|
+
|
|
16
|
+
## 运行条件
|
|
17
|
+
|
|
18
|
+
- Python >= 3.8
|
|
19
|
+
- requests
|
|
20
|
+
|
|
21
|
+
## 安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install multibucket
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 快速开始
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from multibucket import UCloudFileClient
|
|
31
|
+
|
|
32
|
+
# 初始化客户端
|
|
33
|
+
ufile = UCloudFileClient(
|
|
34
|
+
public_key="your_public_key",
|
|
35
|
+
private_key="your_private_key",
|
|
36
|
+
bucket="your-bucket",
|
|
37
|
+
region="cn-bj"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# 上传文件
|
|
41
|
+
with open("image.jpg", "rb") as f:
|
|
42
|
+
resp = ufile.put("photos/image.jpg", f.read(), content_type="image/jpg")
|
|
43
|
+
print(resp.status_code) # 200
|
|
44
|
+
|
|
45
|
+
# 带重试的上传(默认重试 3 次)
|
|
46
|
+
success = ufile.put_with_retry("backup/data.zip", data, retries=3)
|
|
47
|
+
print(success) # True/False
|
|
48
|
+
|
|
49
|
+
# 获取文件 URL
|
|
50
|
+
url = ufile.get_path_url("photos/image.jpg")
|
|
51
|
+
print(url) # https://your-bucket.cn-bj.ufileos.com/photos/image.jpg
|
|
52
|
+
|
|
53
|
+
# 生成分享链接(私有空间,默认 20 分钟有效)
|
|
54
|
+
share_url = ufile.get_shared_url("photos/image.jpg", expire_time=3600)
|
|
55
|
+
print(share_url)
|
|
56
|
+
|
|
57
|
+
# 判断文件是否存在
|
|
58
|
+
exists = ufile.exists("photos/image.jpg")
|
|
59
|
+
print(exists) # True/False
|
|
60
|
+
|
|
61
|
+
# 下载文件
|
|
62
|
+
resp = ufile.get("photos/image.jpg")
|
|
63
|
+
with open("downloaded.jpg", "wb") as f:
|
|
64
|
+
f.write(resp.content)
|
|
65
|
+
|
|
66
|
+
# 查询文件信息
|
|
67
|
+
resp = ufile.head("photos/image.jpg")
|
|
68
|
+
print(resp.headers["Content-Length"])
|
|
69
|
+
|
|
70
|
+
# 列出文件
|
|
71
|
+
resp = ufile.list(prefix="photos/", limit=50)
|
|
72
|
+
files = resp.json()["DataSet"]
|
|
73
|
+
|
|
74
|
+
# 删除文件
|
|
75
|
+
resp = ufile.delete("photos/image.jpg")
|
|
76
|
+
print(resp.status_code) # 204
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 高级功能
|
|
80
|
+
|
|
81
|
+
### 自定义域名(CDN)
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
ufile = UCloudFileClient(
|
|
85
|
+
public_key="your_public_key",
|
|
86
|
+
private_key="your_private_key",
|
|
87
|
+
bucket="your-bucket",
|
|
88
|
+
region="cn-bj",
|
|
89
|
+
domain_url="https://cdn.example.com" # 自定义 CDN 域名
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 内网访问
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
ufile = UCloudFileClient(
|
|
97
|
+
public_key="your_public_key",
|
|
98
|
+
private_key="your_private_key",
|
|
99
|
+
bucket="your-bucket",
|
|
100
|
+
region="cn-wlcb",
|
|
101
|
+
internal=True # 使用内网访问
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 分片上传(大文件)
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
# 自动分片上传,支持进度回调
|
|
109
|
+
def progress_callback(part_number, total_parts, uploaded, total):
|
|
110
|
+
print(f"进度: {part_number + 1}/{total_parts}")
|
|
111
|
+
|
|
112
|
+
result = ufile.multipart_upload(
|
|
113
|
+
key="videos/large.mp4",
|
|
114
|
+
file_path="/path/to/large.mp4",
|
|
115
|
+
content_type="video/mp4",
|
|
116
|
+
callback=progress_callback
|
|
117
|
+
)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
Requires-Dist: requests
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multibucket/ucloud.cp313-win_amd64.pyd,sha256=ypDDATdT8wBFqU-N-WFxz6iqzUCHQvJxkkDSKvIw4vQ,154624
|
|
2
|
+
multibucket/ucloud.pyi,sha256=acmnIq7iEuLjAv16faLdAGtzyI8xl4QpSvsV_mIdDRM,12954
|
|
3
|
+
multibucket/__init__.py,sha256=lfMf-Ui4-n7AUqLQc0scjrnPBO-6HhbGe2ysrlKjQfo,106
|
|
4
|
+
multibucket-0.1.2.dist-info/METADATA,sha256=r86kVm05U-ydwlekGbE5uL_IGBoYlP3KtiRMrRsZUEc,2888
|
|
5
|
+
multibucket-0.1.2.dist-info/WHEEL,sha256=dn77BUfjMOPRkZt-Pyb_fdAFpXI4sKyN3v0I_j_INQ0,114
|
|
6
|
+
multibucket-0.1.2.dist-info/RECORD,,
|