tamar-file-hub-client 0.0.1__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.
Files changed (44) hide show
  1. file_hub_client/__init__.py +88 -0
  2. file_hub_client/client.py +414 -0
  3. file_hub_client/enums/__init__.py +12 -0
  4. file_hub_client/enums/export_format.py +16 -0
  5. file_hub_client/enums/role.py +7 -0
  6. file_hub_client/enums/upload_mode.py +11 -0
  7. file_hub_client/errors/__init__.py +30 -0
  8. file_hub_client/errors/exceptions.py +93 -0
  9. file_hub_client/py.typed +1 -0
  10. file_hub_client/rpc/__init__.py +10 -0
  11. file_hub_client/rpc/async_client.py +312 -0
  12. file_hub_client/rpc/gen/__init__.py +1 -0
  13. file_hub_client/rpc/gen/file_service_pb2.py +74 -0
  14. file_hub_client/rpc/gen/file_service_pb2_grpc.py +533 -0
  15. file_hub_client/rpc/gen/folder_service_pb2.py +53 -0
  16. file_hub_client/rpc/gen/folder_service_pb2_grpc.py +269 -0
  17. file_hub_client/rpc/generate_grpc.py +76 -0
  18. file_hub_client/rpc/protos/file_service.proto +147 -0
  19. file_hub_client/rpc/protos/folder_service.proto +65 -0
  20. file_hub_client/rpc/sync_client.py +313 -0
  21. file_hub_client/schemas/__init__.py +43 -0
  22. file_hub_client/schemas/context.py +160 -0
  23. file_hub_client/schemas/file.py +89 -0
  24. file_hub_client/schemas/folder.py +29 -0
  25. file_hub_client/services/__init__.py +17 -0
  26. file_hub_client/services/file/__init__.py +14 -0
  27. file_hub_client/services/file/async_blob_service.py +482 -0
  28. file_hub_client/services/file/async_file_service.py +257 -0
  29. file_hub_client/services/file/base_file_service.py +103 -0
  30. file_hub_client/services/file/sync_blob_service.py +478 -0
  31. file_hub_client/services/file/sync_file_service.py +255 -0
  32. file_hub_client/services/folder/__init__.py +10 -0
  33. file_hub_client/services/folder/async_folder_service.py +206 -0
  34. file_hub_client/services/folder/sync_folder_service.py +205 -0
  35. file_hub_client/utils/__init__.py +48 -0
  36. file_hub_client/utils/converter.py +108 -0
  37. file_hub_client/utils/download_helper.py +355 -0
  38. file_hub_client/utils/file_utils.py +105 -0
  39. file_hub_client/utils/retry.py +69 -0
  40. file_hub_client/utils/upload_helper.py +527 -0
  41. tamar_file_hub_client-0.0.1.dist-info/METADATA +874 -0
  42. tamar_file_hub_client-0.0.1.dist-info/RECORD +44 -0
  43. tamar_file_hub_client-0.0.1.dist-info/WHEEL +5 -0
  44. tamar_file_hub_client-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,255 @@
1
+ """
2
+ 同步文件服务
3
+ """
4
+ import grpc
5
+ from typing import Optional, Dict, List, Any
6
+
7
+ from .base_file_service import BaseFileService
8
+ from ...rpc.sync_client import SyncGrpcClient
9
+ from ...schemas import (
10
+ File,
11
+ FileListResponse,
12
+ )
13
+ from ...errors import FileNotFoundError
14
+
15
+
16
+ class SyncFileService(BaseFileService):
17
+ """同步文件服务"""
18
+
19
+ def __init__(self, client: SyncGrpcClient):
20
+ """
21
+ 初始化文件服务
22
+
23
+ Args:
24
+ client: 同步gRPC客户端
25
+ """
26
+ self.client = client
27
+
28
+ def generate_share_link(
29
+ self,
30
+ file_id: str,
31
+ *,
32
+ is_public: bool = True,
33
+ access_scope: str = "view",
34
+ expire_seconds: int = 86400,
35
+ max_access: Optional[int] = None,
36
+ share_password: Optional[str] = None,
37
+ **metadata
38
+ ) -> str:
39
+ """
40
+ 生成分享链接
41
+
42
+ Args:
43
+ file_id: 文件ID
44
+ is_public: 是否公开
45
+ access_scope: 访问范围
46
+ expire_seconds: 过期时间(秒)
47
+ max_access: 最大访问次数
48
+ share_password: 访问密码
49
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
50
+
51
+ Returns:
52
+ 分享ID
53
+ """
54
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
55
+
56
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
57
+
58
+ request = file_service_pb2.ShareLinkRequest(
59
+ file_id=file_id,
60
+ is_public=is_public,
61
+ access_scope=access_scope,
62
+ expire_seconds=expire_seconds
63
+ )
64
+
65
+ if max_access is not None:
66
+ request.max_access = max_access
67
+ if share_password:
68
+ request.share_password = share_password
69
+
70
+ # 构建元数据
71
+ grpc_metadata = self.client.build_metadata(**metadata)
72
+
73
+ response = stub.GenerateShareLink(request, metadata=grpc_metadata)
74
+
75
+ return response.file_share_id
76
+
77
+ def visit_file(
78
+ self,
79
+ file_share_id: str,
80
+ access_type: str = "view",
81
+ access_duration: int = 0,
82
+ metadata: Optional[Dict[str, Any]] = None,
83
+ **extra_metadata
84
+ ) -> None:
85
+ """
86
+ 访问文件(通过分享链接)
87
+
88
+ Args:
89
+ file_share_id: 分享ID
90
+ access_type: 访问类型
91
+ access_duration: 访问时长
92
+ metadata: 元数据
93
+ **extra_metadata: 额外的元数据(如 x-org-id, x-user-id 等)
94
+ """
95
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
96
+ from google.protobuf import struct_pb2
97
+
98
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
99
+
100
+ # 转换metadata为Struct
101
+ struct_metadata = struct_pb2.Struct()
102
+ if metadata:
103
+ for key, value in metadata.items():
104
+ struct_metadata[key] = value
105
+
106
+ request = file_service_pb2.FileVisitRequest(
107
+ file_share_id=file_share_id,
108
+ access_type=access_type,
109
+ access_duration=access_duration,
110
+ metadata=struct_metadata
111
+ )
112
+
113
+ # 构建元数据
114
+ grpc_metadata = self.client.build_metadata(**extra_metadata)
115
+
116
+ stub.VisitFile(request, metadata=grpc_metadata)
117
+
118
+ def get_file(self, file_id: str, **metadata) -> File:
119
+ """
120
+ 获取文件信息
121
+
122
+ Args:
123
+ file_id: 文件ID
124
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
125
+
126
+ Returns:
127
+ 文件信息
128
+ """
129
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
130
+
131
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
132
+
133
+ request = file_service_pb2.GetFileRequest(file_id=file_id)
134
+
135
+ # 构建元数据
136
+ grpc_metadata = self.client.build_metadata(**metadata)
137
+
138
+ try:
139
+ response = stub.GetFile(request, metadata=grpc_metadata)
140
+ return self._convert_file_info(response)
141
+ except grpc.RpcError as e:
142
+ if e.code() == grpc.StatusCode.NOT_FOUND:
143
+ raise FileNotFoundError(file_id)
144
+ raise
145
+
146
+ def rename_file(self, file_id: str, new_name: str, **metadata) -> File:
147
+ """
148
+ 重命名文件
149
+
150
+ Args:
151
+ file_id: 文件ID
152
+ new_name: 新文件名
153
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
154
+
155
+ Returns:
156
+ 更新后的文件信息
157
+ """
158
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
159
+
160
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
161
+
162
+ request = file_service_pb2.RenameFileRequest(
163
+ file_id=file_id,
164
+ new_name=new_name
165
+ )
166
+
167
+ # 构建元数据
168
+ grpc_metadata = self.client.build_metadata(**metadata)
169
+
170
+ try:
171
+ response = stub.RenameFile(request, metadata=grpc_metadata)
172
+ return self._convert_file_info(response)
173
+ except grpc.RpcError as e:
174
+ if e.code() == grpc.StatusCode.NOT_FOUND:
175
+ raise FileNotFoundError(file_id)
176
+ raise
177
+
178
+ def delete_file(self, file_id: str, **metadata) -> None:
179
+ """
180
+ 删除文件
181
+
182
+ Args:
183
+ file_id: 文件ID
184
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
185
+ """
186
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
187
+
188
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
189
+
190
+ request = file_service_pb2.DeleteFileRequest(file_id=file_id)
191
+
192
+ # 构建元数据
193
+ grpc_metadata = self.client.build_metadata(**metadata)
194
+
195
+ try:
196
+ stub.DeleteFile(request, metadata=grpc_metadata)
197
+ except grpc.RpcError as e:
198
+ if e.code() == grpc.StatusCode.NOT_FOUND:
199
+ raise FileNotFoundError(file_id)
200
+ raise
201
+
202
+ def list_files(
203
+ self,
204
+ folder_id: Optional[str] = None,
205
+ file_name: Optional[str] = None,
206
+ file_type: Optional[List[str]] = None,
207
+ created_by_role: Optional[str] = None,
208
+ created_by: Optional[str] = None,
209
+ page_size: int = 20,
210
+ page: int = 1,
211
+ **metadata
212
+ ) -> FileListResponse:
213
+ """
214
+ 列出文件
215
+
216
+ Args:
217
+ folder_id: 文件夹ID
218
+ file_name: 文件名过滤
219
+ file_type: 文件类型过滤
220
+ created_by_role: 创建者角色过滤
221
+ created_by: 创建者过滤
222
+ page_size: 每页大小
223
+ page: 页码
224
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
225
+
226
+ Returns:
227
+ 文件列表响应
228
+ """
229
+ from ...rpc.gen import file_service_pb2, file_service_pb2_grpc
230
+
231
+ stub = self.client.get_stub(file_service_pb2_grpc.FileServiceStub)
232
+
233
+ request = file_service_pb2.ListFilesRequest(
234
+ folder_id=folder_id,
235
+ page_size=page_size,
236
+ page=page
237
+ )
238
+
239
+ if file_name:
240
+ request.file_name = file_name
241
+ if file_type:
242
+ request.file_type.extend(file_type)
243
+ if created_by_role:
244
+ request.created_by_role = created_by_role
245
+ if created_by:
246
+ request.created_by = created_by
247
+
248
+ # 构建元数据
249
+ grpc_metadata = self.client.build_metadata(**metadata)
250
+
251
+ response = stub.ListFiles(request, metadata=grpc_metadata)
252
+
253
+ files = [self._convert_file_info(f) for f in response.files]
254
+
255
+ return FileListResponse(files=files)
@@ -0,0 +1,10 @@
1
+ """
2
+ 文件夹服务模块
3
+ """
4
+ from .async_folder_service import AsyncFolderService
5
+ from .sync_folder_service import SyncFolderService
6
+
7
+ __all__ = [
8
+ "AsyncFolderService",
9
+ "SyncFolderService",
10
+ ]
@@ -0,0 +1,206 @@
1
+ """
2
+ 异步文件夹服务
3
+ """
4
+ import grpc
5
+ from typing import Optional, Any
6
+
7
+ from ...rpc.async_client import AsyncGrpcClient
8
+ from ...schemas import (
9
+ FolderInfo,
10
+ FolderListResponse,
11
+ )
12
+ from ...errors import FolderNotFoundError, ValidationError
13
+ from ...utils.retry import retry_with_backoff
14
+
15
+
16
+ class AsyncFolderService:
17
+ """异步文件夹服务"""
18
+
19
+ def __init__(self, client: AsyncGrpcClient):
20
+ """
21
+ 初始化文件夹服务
22
+
23
+ Args:
24
+ client: 异步gRPC客户端
25
+ """
26
+ self.client = client
27
+
28
+ @retry_with_backoff(max_retries=3)
29
+ async def create_folder(
30
+ self,
31
+ folder_name: str,
32
+ *,
33
+ parent_id: Optional[str] = None,
34
+ **metadata
35
+ ) -> FolderInfo:
36
+ """
37
+ 创建文件夹
38
+
39
+ Args:
40
+ folder_name: 文件夹名称
41
+ parent_id: 父文件夹ID
42
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
43
+
44
+ Returns:
45
+ 创建的文件夹信息
46
+ """
47
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
48
+
49
+ stub = await self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
50
+
51
+ request = folder_service_pb2.CreateFolderRequest(
52
+ folder_name=folder_name,
53
+ parent_id=parent_id
54
+ )
55
+
56
+ # 构建元数据
57
+ grpc_metadata = self.client.build_metadata(**metadata)
58
+
59
+ response = await stub.CreateFolder(request, metadata=grpc_metadata)
60
+ return self._convert_folder_info(response)
61
+
62
+ async def rename_folder(self, folder_id: str, new_name: str, **metadata) -> FolderInfo:
63
+ """
64
+ 重命名文件夹
65
+
66
+ Args:
67
+ folder_id: 文件夹ID
68
+ new_name: 新名称
69
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
70
+ """
71
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
72
+
73
+ if not new_name:
74
+ raise ValidationError("文件夹名称不能为空")
75
+
76
+ stub = await self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
77
+
78
+ request = folder_service_pb2.RenameFolderRequest(
79
+ folder_id=folder_id,
80
+ new_name=new_name
81
+ )
82
+
83
+ # 构建元数据
84
+ grpc_metadata = self.client.build_metadata(**metadata)
85
+
86
+ try:
87
+ response = await stub.RenameFolder(request, metadata=grpc_metadata)
88
+ return self._convert_folder_info(response)
89
+ except grpc.RpcError as e:
90
+ if e.code() == grpc.StatusCode.NOT_FOUND:
91
+ raise FolderNotFoundError(folder_id)
92
+ raise
93
+
94
+ async def move_folder(self, folder_id: str, new_parent_id: str, **metadata) -> FolderInfo:
95
+ """
96
+ 移动文件夹
97
+
98
+ Args:
99
+ folder_id: 文件夹ID
100
+ new_parent_id: 新父文件夹ID
101
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
102
+ """
103
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
104
+
105
+ stub = await self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
106
+
107
+ request = folder_service_pb2.MoveFolderRequest(
108
+ folder_id=folder_id,
109
+ new_parent_id=new_parent_id
110
+ )
111
+
112
+ # 构建元数据
113
+ grpc_metadata = self.client.build_metadata(**metadata)
114
+
115
+ try:
116
+ response = await stub.MoveFolder(request, metadata=grpc_metadata)
117
+ return self._convert_folder_info(response)
118
+ except grpc.RpcError as e:
119
+ if e.code() == grpc.StatusCode.NOT_FOUND:
120
+ raise FolderNotFoundError(folder_id)
121
+ raise
122
+
123
+ async def delete_folder(self, folder_id: str, **metadata) -> None:
124
+ """
125
+ 删除文件夹
126
+
127
+ Args:
128
+ folder_id: 文件夹ID
129
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
130
+ """
131
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
132
+
133
+ stub = await self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
134
+
135
+ request = folder_service_pb2.DeleteFolderRequest(folder_id=folder_id)
136
+
137
+ # 构建元数据
138
+ grpc_metadata = self.client.build_metadata(**metadata)
139
+
140
+ try:
141
+ await stub.DeleteFolder(request, metadata=grpc_metadata)
142
+ except grpc.RpcError as e:
143
+ if e.code() == grpc.StatusCode.NOT_FOUND:
144
+ raise FolderNotFoundError(folder_id)
145
+ raise
146
+
147
+ async def list_folders(
148
+ self,
149
+ parent_id: Optional[str] = None,
150
+ folder_name: Optional[str] = None,
151
+ created_by_role: Optional[str] = None,
152
+ created_by: Optional[str] = None,
153
+ **metadata
154
+ ) -> FolderListResponse:
155
+ """
156
+ 列出文件夹
157
+
158
+ Args:
159
+ parent_id: 父文件夹ID
160
+ folder_name: 文件夹名称
161
+ created_by_role: 创建者角色
162
+ created_by: 创建者
163
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
164
+
165
+ Returns:
166
+ 文件夹列表响应
167
+ """
168
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
169
+
170
+ stub = await self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
171
+
172
+ request = folder_service_pb2.ListFoldersRequest()
173
+
174
+ if parent_id:
175
+ request.parent_id = parent_id
176
+ if folder_name:
177
+ request.folder_name = folder_name
178
+ if created_by_role:
179
+ request.created_by_role = created_by_role
180
+ if created_by:
181
+ request.created_by = created_by
182
+
183
+ # 构建元数据
184
+ grpc_metadata = self.client.build_metadata(**metadata)
185
+
186
+ response = await stub.ListFolders(request, metadata=grpc_metadata)
187
+
188
+ folders = [self._convert_folder_info(f) for f in response.items]
189
+
190
+ return FolderListResponse(items=folders)
191
+
192
+ def _convert_folder_info(self, proto_folder: Any) -> FolderInfo:
193
+ """转换Proto文件夹信息为模型"""
194
+ from ...utils.converter import timestamp_to_datetime
195
+
196
+ return FolderInfo(
197
+ id=proto_folder.id,
198
+ org_id=proto_folder.org_id,
199
+ user_id=proto_folder.user_id,
200
+ folder_name=proto_folder.folder_name,
201
+ parent_id=proto_folder.parent_id,
202
+ created_by=proto_folder.created_by,
203
+ created_by_role=proto_folder.created_by_role,
204
+ created_at=timestamp_to_datetime(proto_folder.created_at),
205
+ updated_at=timestamp_to_datetime(proto_folder.updated_at)
206
+ )
@@ -0,0 +1,205 @@
1
+ """
2
+ 同步文件夹服务
3
+ """
4
+ import grpc
5
+ from typing import Optional, Any
6
+
7
+ from ...rpc.sync_client import SyncGrpcClient
8
+ from ...schemas import (
9
+ FolderInfo,
10
+ FolderListResponse,
11
+ )
12
+ from ...errors import FolderNotFoundError, ValidationError
13
+ from ...utils.retry import retry_with_backoff
14
+
15
+
16
+ class SyncFolderService:
17
+ """同步文件夹服务"""
18
+
19
+ def __init__(self, client: SyncGrpcClient):
20
+ """
21
+ 初始化文件夹服务
22
+
23
+ Args:
24
+ client: 同步gRPC客户端
25
+ """
26
+ self.client = client
27
+
28
+ @retry_with_backoff(max_retries=3)
29
+ def create_folder(
30
+ self,
31
+ folder_name: str,
32
+ parent_id: str,
33
+ **metadata
34
+ ) -> FolderInfo:
35
+ """
36
+ 创建文件夹
37
+
38
+ Args:
39
+ folder_name: 文件夹名称
40
+ parent_id: 父文件夹ID
41
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
42
+
43
+ Returns:
44
+ 创建的文件夹信息
45
+ """
46
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
47
+
48
+ stub = self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
49
+
50
+ request = folder_service_pb2.CreateFolderRequest(
51
+ folder_name=folder_name,
52
+ parent_id=parent_id
53
+ )
54
+
55
+ # 构建元数据
56
+ grpc_metadata = self.client.build_metadata(**metadata)
57
+
58
+ response = stub.CreateFolder(request, metadata=grpc_metadata)
59
+ return self._convert_folder_info(response)
60
+
61
+ def rename_folder(self, folder_id: str, new_name: str, **metadata) -> FolderInfo:
62
+ """
63
+ 重命名文件夹
64
+
65
+ Args:
66
+ folder_id: 文件夹ID
67
+ new_name: 新名称
68
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
69
+ """
70
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
71
+
72
+ if not new_name:
73
+ raise ValidationError("文件夹名称不能为空")
74
+
75
+ stub = self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
76
+
77
+ request = folder_service_pb2.RenameFolderRequest(
78
+ folder_id=folder_id,
79
+ new_name=new_name
80
+ )
81
+
82
+ # 构建元数据
83
+ grpc_metadata = self.client.build_metadata(**metadata)
84
+
85
+ try:
86
+ response = stub.RenameFolder(request, metadata=grpc_metadata)
87
+ return self._convert_folder_info(response)
88
+ except grpc.RpcError as e:
89
+ if e.code() == grpc.StatusCode.NOT_FOUND:
90
+ raise FolderNotFoundError(folder_id)
91
+ raise
92
+
93
+ def move_folder(self, folder_id: str, new_parent_id: str, **metadata) -> FolderInfo:
94
+ """
95
+ 移动文件夹
96
+
97
+ Args:
98
+ folder_id: 文件夹ID
99
+ new_parent_id: 新父文件夹ID
100
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
101
+ """
102
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
103
+
104
+ stub = self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
105
+
106
+ request = folder_service_pb2.MoveFolderRequest(
107
+ folder_id=folder_id,
108
+ new_parent_id=new_parent_id
109
+ )
110
+
111
+ # 构建元数据
112
+ grpc_metadata = self.client.build_metadata(**metadata)
113
+
114
+ try:
115
+ response = stub.MoveFolder(request, metadata=grpc_metadata)
116
+ return self._convert_folder_info(response)
117
+ except grpc.RpcError as e:
118
+ if e.code() == grpc.StatusCode.NOT_FOUND:
119
+ raise FolderNotFoundError(folder_id)
120
+ raise
121
+
122
+ def delete_folder(self, folder_id: str, **metadata) -> None:
123
+ """
124
+ 删除文件夹
125
+
126
+ Args:
127
+ folder_id: 文件夹ID
128
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
129
+ """
130
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
131
+
132
+ stub = self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
133
+
134
+ request = folder_service_pb2.DeleteFolderRequest(folder_id=folder_id)
135
+
136
+ # 构建元数据
137
+ grpc_metadata = self.client.build_metadata(**metadata)
138
+
139
+ try:
140
+ stub.DeleteFolder(request, metadata=grpc_metadata)
141
+ except grpc.RpcError as e:
142
+ if e.code() == grpc.StatusCode.NOT_FOUND:
143
+ raise FolderNotFoundError(folder_id)
144
+ raise
145
+
146
+ def list_folders(
147
+ self,
148
+ parent_id: Optional[str] = None,
149
+ folder_name: Optional[str] = None,
150
+ created_by_role: Optional[str] = None,
151
+ created_by: Optional[str] = None,
152
+ **metadata
153
+ ) -> FolderListResponse:
154
+ """
155
+ 列出文件夹
156
+
157
+ Args:
158
+ parent_id: 父文件夹ID
159
+ folder_name: 文件夹名称
160
+ created_by_role: 创建者角色
161
+ created_by: 创建者
162
+ **metadata: 额外的元数据(如 x-org-id, x-user-id 等)
163
+
164
+ Returns:
165
+ 文件夹列表响应
166
+ """
167
+ from ...rpc.gen import folder_service_pb2, folder_service_pb2_grpc
168
+
169
+ stub = self.client.get_stub(folder_service_pb2_grpc.FolderServiceStub)
170
+
171
+ request = folder_service_pb2.ListFoldersRequest()
172
+
173
+ if parent_id:
174
+ request.parent_id = parent_id
175
+ if folder_name:
176
+ request.folder_name = folder_name
177
+ if created_by_role:
178
+ request.created_by_role = created_by_role
179
+ if created_by:
180
+ request.created_by = created_by
181
+
182
+ # 构建元数据
183
+ grpc_metadata = self.client.build_metadata(**metadata)
184
+
185
+ response = stub.ListFolders(request, metadata=grpc_metadata)
186
+
187
+ folders = [self._convert_folder_info(f) for f in response.items]
188
+
189
+ return FolderListResponse(items=folders)
190
+
191
+ def _convert_folder_info(self, proto_folder: Any) -> FolderInfo:
192
+ """转换Proto文件夹信息为模型"""
193
+ from ...utils.converter import timestamp_to_datetime
194
+
195
+ return FolderInfo(
196
+ id=proto_folder.id,
197
+ org_id=proto_folder.org_id,
198
+ user_id=proto_folder.user_id,
199
+ folder_name=proto_folder.folder_name,
200
+ parent_id=proto_folder.parent_id,
201
+ created_by=proto_folder.created_by,
202
+ created_by_role=proto_folder.created_by_role,
203
+ created_at=timestamp_to_datetime(proto_folder.created_at),
204
+ updated_at=timestamp_to_datetime(proto_folder.updated_at)
205
+ )