tamar-file-hub-client 0.1.4__py3-none-any.whl → 0.1.6__py3-none-any.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.
- file_hub_client/rpc/gen/file_service_pb2.py +30 -30
- file_hub_client/rpc/interceptors.py +578 -580
- file_hub_client/rpc/protos/file_service.proto +2 -1
- file_hub_client/schemas/file.py +171 -170
- file_hub_client/services/file/async_blob_service.py +25 -8
- file_hub_client/services/file/base_file_service.py +210 -9
- file_hub_client/services/file/sync_blob_service.py +25 -8
- file_hub_client/utils/__init__.py +10 -0
- file_hub_client/utils/logging.py +335 -318
- file_hub_client/utils/mime_extension_mapper.py +158 -0
- file_hub_client/utils/upload_helper.py +36 -22
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.6.dist-info}/METADATA +67 -2
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.6.dist-info}/RECORD +15 -14
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.6.dist-info}/WHEEL +0 -0
- {tamar_file_hub_client-0.1.4.dist-info → tamar_file_hub_client-0.1.6.dist-info}/top_level.txt +0 -0
file_hub_client/schemas/file.py
CHANGED
@@ -1,170 +1,171 @@
|
|
1
|
-
"""
|
2
|
-
文件相关数据模型
|
3
|
-
"""
|
4
|
-
from datetime import datetime
|
5
|
-
from typing import Optional, Dict, List, Any
|
6
|
-
from pydantic import BaseModel, Field
|
7
|
-
|
8
|
-
|
9
|
-
class File(BaseModel):
|
10
|
-
"""文件信息模型"""
|
11
|
-
id: str = Field(..., description="文件ID")
|
12
|
-
folder_id: str = Field(..., description="所属文件夹ID")
|
13
|
-
file_name: str = Field(..., description="原始文件名")
|
14
|
-
file_type: str = Field(..., description="文件类型")
|
15
|
-
created_at: datetime = Field(..., description="创建时间")
|
16
|
-
updated_at: datetime = Field(..., description="更新时间")
|
17
|
-
|
18
|
-
class Config:
|
19
|
-
json_encoders = {
|
20
|
-
datetime: lambda v: v.isoformat()
|
21
|
-
}
|
22
|
-
|
23
|
-
|
24
|
-
class UploadFile(BaseModel):
|
25
|
-
"""上传文件信息模型"""
|
26
|
-
id: str = Field(..., description="上传文件ID")
|
27
|
-
folder_id: str = Field(..., description="所属文件夹ID")
|
28
|
-
storage_type: str = Field(..., description="存储类型")
|
29
|
-
stored_name: str = Field(..., description="存储文件名")
|
30
|
-
stored_path: str = Field(..., description="存储路径")
|
31
|
-
file_id: str = Field(..., description="所属文件ID")
|
32
|
-
file_name: str = Field(..., description="原始文件名")
|
33
|
-
file_size: int = Field(0, description="文件大小(字节)")
|
34
|
-
file_ext: str = Field(..., description="文件后缀")
|
35
|
-
mime_type: str = Field(..., description="MIME类型")
|
36
|
-
created_at: datetime = Field(..., description="创建时间")
|
37
|
-
updated_at: datetime = Field(..., description="更新时间")
|
38
|
-
|
39
|
-
class Config:
|
40
|
-
json_encoders = {
|
41
|
-
datetime: lambda v: v.isoformat()
|
42
|
-
}
|
43
|
-
|
44
|
-
|
45
|
-
class FileUploadResponse(BaseModel):
|
46
|
-
"""文件上传返回"""
|
47
|
-
file: File = Field(..., description="文件信息")
|
48
|
-
upload_file: UploadFile = Field(..., description="上传文件信息")
|
49
|
-
|
50
|
-
|
51
|
-
class UploadUrlResponse(BaseModel):
|
52
|
-
"""上传URL响应"""
|
53
|
-
file: File = Field(..., description="文件信息")
|
54
|
-
upload_file: UploadFile = Field(..., description="上传文件信息")
|
55
|
-
upload_url: str = Field(..., description="上传URL")
|
56
|
-
|
57
|
-
|
58
|
-
class ShareLinkRequest(BaseModel):
|
59
|
-
"""生成分享链接请求"""
|
60
|
-
file_id: str = Field(..., description="文件ID")
|
61
|
-
is_public: bool = Field(True, description="是否公开")
|
62
|
-
access_scope: str = Field("view", description="访问范围")
|
63
|
-
expire_seconds: int = Field(86400, description="过期时间(秒)")
|
64
|
-
max_access: Optional[int] = Field(None, description="最大访问次数")
|
65
|
-
password: Optional[str] = Field(None, description="访问密码")
|
66
|
-
|
67
|
-
|
68
|
-
class FileVisitRequest(BaseModel):
|
69
|
-
"""文件访问请求"""
|
70
|
-
file_share_id: str = Field(..., description="分享ID")
|
71
|
-
access_type: str = Field(..., description="访问类型")
|
72
|
-
access_duration: int = Field(..., description="访问时长")
|
73
|
-
metadata: Dict[str, Any] = Field(default_factory=dict, description="元数据")
|
74
|
-
|
75
|
-
|
76
|
-
class FileListRequest(BaseModel):
|
77
|
-
"""文件列表请求"""
|
78
|
-
folder_id: str = Field(..., description="文件夹ID")
|
79
|
-
file_name: Optional[str] = Field(None, description="文件名")
|
80
|
-
file_type: Optional[List[str]] = Field(None, description="文件类型")
|
81
|
-
created_by_role: Optional[str] = Field(None, description="创建者角色")
|
82
|
-
created_by: Optional[str] = Field(None, description="创建者")
|
83
|
-
page_size: int = Field(20, description="每页大小")
|
84
|
-
page: int = Field(1, description="页码")
|
85
|
-
|
86
|
-
|
87
|
-
class FileListResponse(BaseModel):
|
88
|
-
"""文件列表响应"""
|
89
|
-
files: List[File] = Field(default_factory=list, description="文件列表")
|
90
|
-
|
91
|
-
|
92
|
-
class GetFileResponse(BaseModel):
|
93
|
-
"""获取文件响应"""
|
94
|
-
file: File = Field(..., description="文件信息")
|
95
|
-
upload_file: Optional[UploadFile] = Field(None, description="上传文件信息")
|
96
|
-
|
97
|
-
|
98
|
-
class DownloadUrlInfo(BaseModel):
|
99
|
-
"""下载URL信息"""
|
100
|
-
file_id: str = Field(..., description="文件ID")
|
101
|
-
url: str = Field(..., description="下载URL")
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
1
|
+
"""
|
2
|
+
文件相关数据模型
|
3
|
+
"""
|
4
|
+
from datetime import datetime
|
5
|
+
from typing import Optional, Dict, List, Any
|
6
|
+
from pydantic import BaseModel, Field
|
7
|
+
|
8
|
+
|
9
|
+
class File(BaseModel):
|
10
|
+
"""文件信息模型"""
|
11
|
+
id: str = Field(..., description="文件ID")
|
12
|
+
folder_id: str = Field(..., description="所属文件夹ID")
|
13
|
+
file_name: str = Field(..., description="原始文件名")
|
14
|
+
file_type: str = Field(..., description="文件类型")
|
15
|
+
created_at: datetime = Field(..., description="创建时间")
|
16
|
+
updated_at: datetime = Field(..., description="更新时间")
|
17
|
+
|
18
|
+
class Config:
|
19
|
+
json_encoders = {
|
20
|
+
datetime: lambda v: v.isoformat()
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
class UploadFile(BaseModel):
|
25
|
+
"""上传文件信息模型"""
|
26
|
+
id: str = Field(..., description="上传文件ID")
|
27
|
+
folder_id: str = Field(..., description="所属文件夹ID")
|
28
|
+
storage_type: str = Field(..., description="存储类型")
|
29
|
+
stored_name: str = Field(..., description="存储文件名")
|
30
|
+
stored_path: str = Field(..., description="存储路径")
|
31
|
+
file_id: str = Field(..., description="所属文件ID")
|
32
|
+
file_name: str = Field(..., description="原始文件名")
|
33
|
+
file_size: int = Field(0, description="文件大小(字节)")
|
34
|
+
file_ext: str = Field(..., description="文件后缀")
|
35
|
+
mime_type: str = Field(..., description="MIME类型")
|
36
|
+
created_at: datetime = Field(..., description="创建时间")
|
37
|
+
updated_at: datetime = Field(..., description="更新时间")
|
38
|
+
|
39
|
+
class Config:
|
40
|
+
json_encoders = {
|
41
|
+
datetime: lambda v: v.isoformat()
|
42
|
+
}
|
43
|
+
|
44
|
+
|
45
|
+
class FileUploadResponse(BaseModel):
|
46
|
+
"""文件上传返回"""
|
47
|
+
file: File = Field(..., description="文件信息")
|
48
|
+
upload_file: UploadFile = Field(..., description="上传文件信息")
|
49
|
+
|
50
|
+
|
51
|
+
class UploadUrlResponse(BaseModel):
|
52
|
+
"""上传URL响应"""
|
53
|
+
file: File = Field(..., description="文件信息")
|
54
|
+
upload_file: UploadFile = Field(..., description="上传文件信息")
|
55
|
+
upload_url: str = Field(..., description="上传URL")
|
56
|
+
|
57
|
+
|
58
|
+
class ShareLinkRequest(BaseModel):
|
59
|
+
"""生成分享链接请求"""
|
60
|
+
file_id: str = Field(..., description="文件ID")
|
61
|
+
is_public: bool = Field(True, description="是否公开")
|
62
|
+
access_scope: str = Field("view", description="访问范围")
|
63
|
+
expire_seconds: int = Field(86400, description="过期时间(秒)")
|
64
|
+
max_access: Optional[int] = Field(None, description="最大访问次数")
|
65
|
+
password: Optional[str] = Field(None, description="访问密码")
|
66
|
+
|
67
|
+
|
68
|
+
class FileVisitRequest(BaseModel):
|
69
|
+
"""文件访问请求"""
|
70
|
+
file_share_id: str = Field(..., description="分享ID")
|
71
|
+
access_type: str = Field(..., description="访问类型")
|
72
|
+
access_duration: int = Field(..., description="访问时长")
|
73
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="元数据")
|
74
|
+
|
75
|
+
|
76
|
+
class FileListRequest(BaseModel):
|
77
|
+
"""文件列表请求"""
|
78
|
+
folder_id: str = Field(..., description="文件夹ID")
|
79
|
+
file_name: Optional[str] = Field(None, description="文件名")
|
80
|
+
file_type: Optional[List[str]] = Field(None, description="文件类型")
|
81
|
+
created_by_role: Optional[str] = Field(None, description="创建者角色")
|
82
|
+
created_by: Optional[str] = Field(None, description="创建者")
|
83
|
+
page_size: int = Field(20, description="每页大小")
|
84
|
+
page: int = Field(1, description="页码")
|
85
|
+
|
86
|
+
|
87
|
+
class FileListResponse(BaseModel):
|
88
|
+
"""文件列表响应"""
|
89
|
+
files: List[File] = Field(default_factory=list, description="文件列表")
|
90
|
+
|
91
|
+
|
92
|
+
class GetFileResponse(BaseModel):
|
93
|
+
"""获取文件响应"""
|
94
|
+
file: File = Field(..., description="文件信息")
|
95
|
+
upload_file: Optional[UploadFile] = Field(None, description="上传文件信息")
|
96
|
+
|
97
|
+
|
98
|
+
class DownloadUrlInfo(BaseModel):
|
99
|
+
"""下载URL信息"""
|
100
|
+
file_id: str = Field(..., description="文件ID")
|
101
|
+
url: str = Field(..., description="下载URL")
|
102
|
+
mime_type: str = Field(..., description="MIME类型")
|
103
|
+
error: Optional[str] = Field(None, description="错误信息")
|
104
|
+
|
105
|
+
|
106
|
+
class BatchDownloadUrlResponse(BaseModel):
|
107
|
+
"""批量下载URL响应"""
|
108
|
+
download_urls: List[DownloadUrlInfo] = Field(default_factory=list, description="下载URL列表")
|
109
|
+
|
110
|
+
|
111
|
+
class GcsUrlInfo(BaseModel):
|
112
|
+
"""GCS URL信息"""
|
113
|
+
file_id: str = Field(..., description="文件ID")
|
114
|
+
gcs_url: str = Field(..., description="GCS URL")
|
115
|
+
mime_type: str = Field(..., description="MIME类型")
|
116
|
+
error: Optional[str] = Field(None, description="错误信息")
|
117
|
+
|
118
|
+
|
119
|
+
class GetGcsUrlResponse(BaseModel):
|
120
|
+
"""获取GCS URL响应"""
|
121
|
+
gcs_url: str = Field(..., description="GCS URL")
|
122
|
+
mime_type: str = Field(..., description="MIME类型")
|
123
|
+
|
124
|
+
|
125
|
+
class BatchGcsUrlResponse(BaseModel):
|
126
|
+
"""批量GCS URL响应"""
|
127
|
+
gcs_urls: List[GcsUrlInfo] = Field(default_factory=list, description="GCS URL列表")
|
128
|
+
|
129
|
+
|
130
|
+
# ========= 压缩服务相关模型 =========
|
131
|
+
|
132
|
+
class CompressedVariant(BaseModel):
|
133
|
+
"""压缩变体信息"""
|
134
|
+
variant_name: str = Field(..., description="变体名称")
|
135
|
+
variant_type: str = Field(..., description="变体类型")
|
136
|
+
media_type: str = Field(..., description="媒体类型")
|
137
|
+
width: int = Field(..., description="宽度")
|
138
|
+
height: int = Field(..., description="高度")
|
139
|
+
file_size: int = Field(..., description="文件大小")
|
140
|
+
format: str = Field(..., description="格式")
|
141
|
+
quality: Optional[int] = Field(None, description="质量")
|
142
|
+
duration: Optional[float] = Field(None, description="时长")
|
143
|
+
bitrate: Optional[int] = Field(None, description="比特率")
|
144
|
+
fps: Optional[int] = Field(None, description="帧率")
|
145
|
+
compression_ratio: float = Field(..., description="压缩比")
|
146
|
+
stored_path: str = Field(..., description="存储路径")
|
147
|
+
|
148
|
+
|
149
|
+
class CompressionStatusResponse(BaseModel):
|
150
|
+
"""压缩状态响应"""
|
151
|
+
status: str = Field(..., description="状态: pending, processing, completed, failed")
|
152
|
+
error_message: Optional[str] = Field(None, description="错误信息")
|
153
|
+
variants: List[CompressedVariant] = Field(default_factory=list, description="压缩变体列表")
|
154
|
+
|
155
|
+
|
156
|
+
class GetVariantsResponse(BaseModel):
|
157
|
+
"""获取变体响应"""
|
158
|
+
variants: List[CompressedVariant] = Field(default_factory=list, description="压缩变体列表")
|
159
|
+
|
160
|
+
|
161
|
+
class RecompressionResponse(BaseModel):
|
162
|
+
"""重新压缩响应"""
|
163
|
+
task_id: str = Field(..., description="任务ID")
|
164
|
+
status: str = Field(..., description="状态")
|
165
|
+
|
166
|
+
|
167
|
+
class VariantDownloadUrlResponse(BaseModel):
|
168
|
+
"""变体下载URL响应"""
|
169
|
+
url: str = Field(..., description="下载URL")
|
170
|
+
error: Optional[str] = Field(None, description="错误信息")
|
171
|
+
variant_info: Optional[CompressedVariant] = Field(None, description="变体详细信息")
|
@@ -221,7 +221,7 @@ class AsyncBlobService(BaseFileService):
|
|
221
221
|
upload_url = await self.http_uploader.start_resumable_session(
|
222
222
|
url=upload_url_resp.upload_url,
|
223
223
|
total_file_size=file_size,
|
224
|
-
|
224
|
+
mime_type=mime_type,
|
225
225
|
)
|
226
226
|
|
227
227
|
# 上传文件到对象存储
|
@@ -403,6 +403,7 @@ class AsyncBlobService(BaseFileService):
|
|
403
403
|
keep_original_filename: Optional[bool] = False,
|
404
404
|
url: Optional[str] = None,
|
405
405
|
file_name: Optional[str] = None,
|
406
|
+
mime_type: Optional[str] = None,
|
406
407
|
request_id: Optional[str] = None,
|
407
408
|
**metadata
|
408
409
|
) -> FileUploadResponse:
|
@@ -418,6 +419,7 @@ class AsyncBlobService(BaseFileService):
|
|
418
419
|
keep_original_filename: 是否保留原始文件名(默认False)
|
419
420
|
url: 要下载并上传的URL(可选)
|
420
421
|
file_name: 当使用url参数时指定的文件名(可选)
|
422
|
+
mime_type: MIME类型(可选,用于推断文件扩展名,特别适用于AI生成的字节数据)
|
421
423
|
request_id: 请求ID(可选,如果不提供则自动生成)
|
422
424
|
**metadata: 额外的元数据
|
423
425
|
|
@@ -427,6 +429,8 @@ class AsyncBlobService(BaseFileService):
|
|
427
429
|
Note:
|
428
430
|
必须提供 file 或 url 参数之一
|
429
431
|
|
432
|
+
当传入bytes或BinaryIO且未提供file_name时,建议提供mime_type以确保正确的文件扩展名推断
|
433
|
+
|
430
434
|
Cache-Control 头在 GCS 直传模式(STREAM/RESUMABLE)下自动设置为 "public, max-age=86400"
|
431
435
|
"""
|
432
436
|
# 参数验证:必须提供 file 或 url 之一
|
@@ -449,26 +453,38 @@ class AsyncBlobService(BaseFileService):
|
|
449
453
|
# 使用下载的内容作为file参数
|
450
454
|
file = downloaded_content
|
451
455
|
|
452
|
-
#
|
453
|
-
|
456
|
+
# 基于文件名计算MIME类型
|
457
|
+
mime_type = get_file_mime_type(Path(file_name))
|
458
|
+
|
459
|
+
# 提取文件信息,传入MIME类型用于推断扩展名
|
460
|
+
_, content, file_size, _, _, file_hash = self._extract_file_info(file, mime_type)
|
454
461
|
|
455
462
|
# file_name已经在上面设置了(要么是用户指定的,要么是从URL提取的)
|
456
463
|
extracted_file_name = file_name
|
457
464
|
|
458
|
-
#
|
465
|
+
# 基于文件名计算文件类型
|
459
466
|
file_type = Path(extracted_file_name).suffix.lstrip('.').lower() if Path(
|
460
467
|
extracted_file_name).suffix else 'dat'
|
461
|
-
mime_type = get_file_mime_type(Path(extracted_file_name))
|
462
468
|
else:
|
463
469
|
# 解析文件参数,提取文件信息
|
464
|
-
|
465
|
-
file)
|
470
|
+
# 如果用户指定了文件名,先从文件名推断MIME类型,然后传给_extract_file_info
|
466
471
|
if file_name:
|
472
|
+
# 用户指定了文件名,优先使用用户提供的MIME类型,否则从文件名推断
|
473
|
+
if mime_type:
|
474
|
+
file_name_mime_type = mime_type
|
475
|
+
else:
|
476
|
+
file_name_mime_type = get_file_mime_type(Path(file_name))
|
477
|
+
extracted_file_name, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(
|
478
|
+
file, file_name_mime_type)
|
479
|
+
# 使用用户指定的文件名
|
467
480
|
extracted_file_name = file_name
|
468
|
-
mime_type =
|
481
|
+
mime_type = file_name_mime_type
|
469
482
|
file_type = Path(extracted_file_name).suffix.lstrip('.').lower() if Path(
|
470
483
|
extracted_file_name).suffix else 'dat'
|
471
484
|
else:
|
485
|
+
# 没有指定文件名,传入用户提供的MIME类型(如果有)
|
486
|
+
extracted_file_name, content, file_size, extract_mime_type, extract_file_type, file_hash = self._extract_file_info(
|
487
|
+
file, mime_type)
|
472
488
|
mime_type = extract_mime_type
|
473
489
|
file_type = extract_file_type
|
474
490
|
|
@@ -670,6 +686,7 @@ class AsyncBlobService(BaseFileService):
|
|
670
686
|
download_urls.append(DownloadUrlInfo(
|
671
687
|
file_id=url_info.file_id,
|
672
688
|
url=url_info.url,
|
689
|
+
mime_type=url_info.mime_type,
|
673
690
|
error=url_info.error if url_info.HasField('error') else None
|
674
691
|
))
|
675
692
|
|