tamar-file-hub-client 0.0.1__py3-none-any.whl → 0.0.2__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 (36) hide show
  1. file_hub_client/__init__.py +39 -0
  2. file_hub_client/client.py +43 -6
  3. file_hub_client/rpc/async_client.py +91 -11
  4. file_hub_client/rpc/gen/taple_service_pb2.py +225 -0
  5. file_hub_client/rpc/gen/taple_service_pb2_grpc.py +1626 -0
  6. file_hub_client/rpc/generate_grpc.py +2 -2
  7. file_hub_client/rpc/interceptors.py +550 -0
  8. file_hub_client/rpc/protos/taple_service.proto +874 -0
  9. file_hub_client/rpc/sync_client.py +91 -9
  10. file_hub_client/schemas/__init__.py +60 -0
  11. file_hub_client/schemas/taple.py +413 -0
  12. file_hub_client/services/__init__.py +5 -0
  13. file_hub_client/services/file/async_blob_service.py +558 -482
  14. file_hub_client/services/file/async_file_service.py +18 -9
  15. file_hub_client/services/file/base_file_service.py +19 -6
  16. file_hub_client/services/file/sync_blob_service.py +554 -478
  17. file_hub_client/services/file/sync_file_service.py +18 -9
  18. file_hub_client/services/folder/async_folder_service.py +20 -11
  19. file_hub_client/services/folder/sync_folder_service.py +20 -11
  20. file_hub_client/services/taple/__init__.py +10 -0
  21. file_hub_client/services/taple/async_taple_service.py +2281 -0
  22. file_hub_client/services/taple/base_taple_service.py +353 -0
  23. file_hub_client/services/taple/idempotent_taple_mixin.py +142 -0
  24. file_hub_client/services/taple/sync_taple_service.py +2256 -0
  25. file_hub_client/utils/__init__.py +43 -1
  26. file_hub_client/utils/file_utils.py +59 -11
  27. file_hub_client/utils/idempotency.py +196 -0
  28. file_hub_client/utils/logging.py +315 -0
  29. file_hub_client/utils/retry.py +241 -2
  30. file_hub_client/utils/smart_retry.py +403 -0
  31. tamar_file_hub_client-0.0.2.dist-info/METADATA +2050 -0
  32. tamar_file_hub_client-0.0.2.dist-info/RECORD +57 -0
  33. tamar_file_hub_client-0.0.1.dist-info/METADATA +0 -874
  34. tamar_file_hub_client-0.0.1.dist-info/RECORD +0 -44
  35. {tamar_file_hub_client-0.0.1.dist-info → tamar_file_hub_client-0.0.2.dist-info}/WHEEL +0 -0
  36. {tamar_file_hub_client-0.0.1.dist-info → tamar_file_hub_client-0.0.2.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,9 @@ from typing import Optional, Dict, List, Tuple
14
14
  from ..enums import Role
15
15
  from ..errors import ConnectionError
16
16
  from ..schemas.context import UserContext, RequestContext, FullContext
17
+ from ..utils.logging import get_logger, setup_logging
18
+ from .interceptors import create_sync_interceptors
19
+ import logging
17
20
 
18
21
 
19
22
  class SyncGrpcClient:
@@ -22,7 +25,7 @@ class SyncGrpcClient:
22
25
  def __init__(
23
26
  self,
24
27
  host: str = "localhost",
25
- port: int = 50051,
28
+ port: Optional[int] = None,
26
29
  secure: bool = False,
27
30
  credentials: Optional[dict] = None,
28
31
  options: Optional[list] = None,
@@ -31,13 +34,15 @@ class SyncGrpcClient:
31
34
  default_metadata: Optional[Dict[str, str]] = None,
32
35
  user_context: Optional[UserContext] = None,
33
36
  request_context: Optional[RequestContext] = None,
37
+ enable_logging: bool = True,
38
+ log_level: str = "INFO",
34
39
  ):
35
40
  """
36
41
  初始化同步gRPC客户端
37
42
 
38
43
  Args:
39
- host: 服务器地址
40
- port: 服务器端口
44
+ host: 服务器地址(可以是域名或IP)
45
+ port: 服务器端口(可选,如果不指定则根据secure自动选择)
41
46
  secure: 是否使用安全连接(TLS)
42
47
  credentials: 认证凭据字典(如 {'api_key': 'xxx'})
43
48
  options: gRPC通道选项
@@ -46,10 +51,19 @@ class SyncGrpcClient:
46
51
  default_metadata: 默认的元数据(如 org_id, user_id 等)
47
52
  user_context: 用户上下文
48
53
  request_context: 请求上下文
54
+ enable_logging: 是否启用日志记录
55
+ log_level: 日志级别
49
56
  """
50
57
  self.host = host
51
58
  self.port = port
52
- self.address = f"{host}:{port}"
59
+
60
+ # 构建地址:如果没有指定端口,则使用域名作为地址
61
+ if port is not None:
62
+ self.address = f"{host}:{port}"
63
+ else:
64
+ # 如果没有指定端口,直接使用host
65
+ # gRPC会自动根据secure选择默认端口(80/443)
66
+ self.address = host
53
67
  self.secure = secure
54
68
  self.credentials = credentials
55
69
  self.options = options or []
@@ -59,6 +73,20 @@ class SyncGrpcClient:
59
73
  self._channel: Optional[grpc.Channel] = None
60
74
  self._stubs = {}
61
75
  self._stub_lock = threading.Lock()
76
+
77
+ # 日志配置
78
+ self.enable_logging = enable_logging
79
+ self.log_level = log_level
80
+
81
+ # 只有在明确启用日志时才设置SDK日志
82
+ # 默认不设置,让用户自己控制
83
+ if enable_logging:
84
+ # 检查是否已经有处理器,避免重复设置
85
+ sdk_logger = get_logger()
86
+ if not sdk_logger.handlers:
87
+ setup_logging(level=log_level, enable_grpc_logging=True, use_json_format=True)
88
+
89
+ self.logger = get_logger()
62
90
 
63
91
  # 上下文管理
64
92
  self._user_context = user_context
@@ -86,7 +114,7 @@ class SyncGrpcClient:
86
114
  return RequestContext(
87
115
  client_ip=client_ip,
88
116
  client_type="python-sdk",
89
- client_version="1.0.0", # TODO: 从包版本获取
117
+ client_version="1.0.0",
90
118
  user_agent=f"FileHubClient/1.0.0 Python/{platform.python_version()} {platform.system()}/{platform.release()}"
91
119
  )
92
120
 
@@ -126,6 +154,9 @@ class SyncGrpcClient:
126
154
 
127
155
  channel_credentials = self._create_channel_credentials()
128
156
 
157
+ # 创建拦截器
158
+ interceptors = create_sync_interceptors() if self.enable_logging else []
159
+
129
160
  if channel_credentials:
130
161
  self._channel = grpc.secure_channel(
131
162
  self.address,
@@ -137,6 +168,10 @@ class SyncGrpcClient:
137
168
  self.address,
138
169
  options=self.options
139
170
  )
171
+
172
+ # 如果有拦截器,需要包装channel
173
+ if interceptors:
174
+ self._channel = grpc.intercept_channel(self._channel, *interceptors)
140
175
 
141
176
  # 连接
142
177
  try:
@@ -145,12 +180,41 @@ class SyncGrpcClient:
145
180
  raise ConnectionError(f"连接超时:{self.address}")
146
181
 
147
182
  # 连接成功
183
+ if self.enable_logging:
184
+ log_record = logging.LogRecord(
185
+ name=self.logger.name,
186
+ level=logging.INFO,
187
+ pathname="",
188
+ lineno=0,
189
+ msg=f"🔗 已连接到 gRPC 服务器",
190
+ args=(),
191
+ exc_info=None
192
+ )
193
+ log_record.log_type = "info"
194
+ log_record.data = {"server": self.address}
195
+ self.logger.handle(log_record)
148
196
  return
149
197
 
150
198
  except Exception as e:
151
199
  last_error = e
152
200
  if attempt < self.retry_count - 1:
153
- print(f"连接失败 (尝试 {attempt + 1}/{self.retry_count}): {str(e)}")
201
+ if self.enable_logging:
202
+ log_record = logging.LogRecord(
203
+ name=self.logger.name,
204
+ level=logging.WARNING,
205
+ pathname="",
206
+ lineno=0,
207
+ msg=f"⚠️ 连接失败,正在重试",
208
+ args=(),
209
+ exc_info=None
210
+ )
211
+ log_record.log_type = "info"
212
+ log_record.data = {
213
+ "attempt": attempt + 1,
214
+ "max_attempts": self.retry_count,
215
+ "error": str(e)
216
+ }
217
+ self.logger.handle(log_record)
154
218
  if self._channel:
155
219
  self._channel.close()
156
220
  self._channel = None
@@ -168,6 +232,19 @@ class SyncGrpcClient:
168
232
  def close(self):
169
233
  """关闭连接"""
170
234
  if self._channel:
235
+ if self.enable_logging:
236
+ log_record = logging.LogRecord(
237
+ name=self.logger.name,
238
+ level=logging.INFO,
239
+ pathname="",
240
+ lineno=0,
241
+ msg=f"👋 正在关闭 gRPC 连接",
242
+ args=(),
243
+ exc_info=None
244
+ )
245
+ log_record.log_type = "info"
246
+ log_record.data = {"server": self.address}
247
+ self.logger.handle(log_record)
171
248
  self._channel.close()
172
249
  self._channel = None
173
250
  self._stubs.clear()
@@ -191,11 +268,12 @@ class SyncGrpcClient:
191
268
  self._stubs[stub_name] = stub_class(self._channel)
192
269
  return self._stubs[stub_name]
193
270
 
194
- def build_metadata(self, **kwargs) -> List[Tuple[str, str]]:
271
+ def build_metadata(self, *, request_id: Optional[str] = None, **kwargs) -> List[Tuple[str, str]]:
195
272
  """
196
273
  构建请求元数据
197
274
 
198
275
  Args:
276
+ request_id: 显式指定的请求ID,如果提供则优先使用
199
277
  **kwargs: 要覆盖或添加的元数据
200
278
 
201
279
  Returns:
@@ -209,8 +287,12 @@ class SyncGrpcClient:
209
287
  # 添加/覆盖传入的元数据
210
288
  metadata.update(kwargs)
211
289
 
212
- # 如果没有 request_id,自动生成一个
213
- if 'x-request-id' not in metadata:
290
+ # 处理 request_id(优先级:显式传入 > metadata中的x-request-id > RequestContext > 自动生成)
291
+ if request_id is not None:
292
+ # 优先使用显式传入的 request_id
293
+ metadata['x-request-id'] = request_id
294
+ elif 'x-request-id' not in metadata:
295
+ # 如果没有显式传入且metadata中也没有,则尝试从RequestContext获取或自动生成
214
296
  metadata['x-request-id'] = (
215
297
  self._request_context.extra.get("request_id") or str(uuid.uuid4())
216
298
  )
@@ -20,6 +20,36 @@ from .context import (
20
20
  RequestContext,
21
21
  FullContext,
22
22
  )
23
+ from .taple import (
24
+ Table,
25
+ Sheet,
26
+ Column,
27
+ Row,
28
+ Cell,
29
+ MergedCell,
30
+ TableView,
31
+ CellUpdate,
32
+ TableResponse,
33
+ SheetResponse,
34
+ ColumnResponse,
35
+ RowResponse,
36
+ CellResponse,
37
+ MergedCellResponse,
38
+ ViewResponse,
39
+ ListSheetsResponse,
40
+ ListColumnsResponse,
41
+ ListRowsResponse,
42
+ BatchCreateRowsResponse,
43
+ BatchUpdateCellsResponse,
44
+ GetCellsByRangeResponse,
45
+ ListMergedCellsResponse,
46
+ ListViewsResponse,
47
+ ImportTableDataResponse,
48
+ TableViewResponse,
49
+ BatchCreateTableViewResult,
50
+ BatchCreateTableViewsResponse,
51
+ ListTableViewsResponse,
52
+ )
23
53
 
24
54
  __all__ = [
25
55
  # 文件相关
@@ -40,4 +70,34 @@ __all__ = [
40
70
  "UserContext",
41
71
  "RequestContext",
42
72
  "FullContext",
73
+
74
+ # Taple 相关
75
+ "Table",
76
+ "Sheet",
77
+ "Column",
78
+ "Row",
79
+ "Cell",
80
+ "MergedCell",
81
+ "TableView",
82
+ "CellUpdate",
83
+ "TableResponse",
84
+ "SheetResponse",
85
+ "ColumnResponse",
86
+ "RowResponse",
87
+ "CellResponse",
88
+ "MergedCellResponse",
89
+ "ViewResponse",
90
+ "ListSheetsResponse",
91
+ "ListColumnsResponse",
92
+ "ListRowsResponse",
93
+ "BatchCreateRowsResponse",
94
+ "BatchUpdateCellsResponse",
95
+ "GetCellsByRangeResponse",
96
+ "ListMergedCellsResponse",
97
+ "ListViewsResponse",
98
+ "ImportTableDataResponse",
99
+ "TableViewResponse",
100
+ "BatchCreateTableViewResult",
101
+ "BatchCreateTableViewsResponse",
102
+ "ListTableViewsResponse",
43
103
  ]
@@ -0,0 +1,413 @@
1
+ """
2
+ Taple 相关数据模型
3
+ """
4
+ from datetime import datetime
5
+ from typing import Optional, List, Dict, Any
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class Table(BaseModel):
10
+ """表格信息模型"""
11
+ id: str = Field(..., description="表格ID")
12
+ file_id: str = Field(..., description="关联文件ID")
13
+ org_id: str = Field(..., description="组织ID")
14
+ user_id: str = Field(..., description="用户ID")
15
+ name: Optional[str] = Field(None, description="表格名称")
16
+ description: Optional[str] = Field(None, description="表格描述")
17
+ created_by_role: str = Field(..., description="创建者角色")
18
+ created_by: str = Field(..., description="创建者ID")
19
+ created_at: datetime = Field(..., description="创建时间")
20
+ updated_at: datetime = Field(..., description="更新时间")
21
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
22
+
23
+ class Config:
24
+ json_encoders = {
25
+ datetime: lambda v: v.isoformat()
26
+ }
27
+
28
+
29
+ class Sheet(BaseModel):
30
+ """工作表信息模型"""
31
+ id: str = Field(..., description="工作表ID")
32
+ table_id: str = Field(..., description="所属表格ID")
33
+ org_id: str = Field(..., description="组织ID")
34
+ user_id: str = Field(..., description="用户ID")
35
+ name: str = Field(..., description="工作表名称")
36
+ description: Optional[str] = Field(None, description="工作表描述")
37
+ position: int = Field(..., description="工作表位置")
38
+ version: int = Field(..., description="版本号")
39
+ created_by_role: str = Field(..., description="创建者角色")
40
+ created_by: str = Field(..., description="创建者ID")
41
+ created_at: datetime = Field(..., description="创建时间")
42
+ updated_at: datetime = Field(..., description="更新时间")
43
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
44
+
45
+ class Config:
46
+ json_encoders = {
47
+ datetime: lambda v: v.isoformat()
48
+ }
49
+
50
+
51
+ class Column(BaseModel):
52
+ """列信息模型"""
53
+ id: str = Field(..., description="列ID")
54
+ sheet_id: str = Field(..., description="所属工作表ID")
55
+ org_id: str = Field(..., description="组织ID")
56
+ user_id: str = Field(..., description="用户ID")
57
+ column_key: str = Field(..., description="列索引key")
58
+ name: str = Field(..., description="列名称")
59
+ column_type: str = Field(..., description="列数据类型")
60
+ description: Optional[str] = Field(None, description="列描述信息")
61
+ position: int = Field(..., description="列位置")
62
+ width: Optional[int] = Field(None, description="列宽度")
63
+ hidden: Optional[bool] = Field(None, description="是否隐藏")
64
+ properties: Optional[Dict[str, Any]] = Field(None, description="列属性")
65
+ version: int = Field(..., description="版本号")
66
+ created_by_role: str = Field(..., description="创建者角色")
67
+ created_by: str = Field(..., description="创建者ID")
68
+ created_at: datetime = Field(..., description="创建时间")
69
+ updated_at: datetime = Field(..., description="更新时间")
70
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
71
+
72
+ class Config:
73
+ json_encoders = {
74
+ datetime: lambda v: v.isoformat()
75
+ }
76
+
77
+
78
+ class Row(BaseModel):
79
+ """行信息模型"""
80
+ id: str = Field(..., description="行ID")
81
+ sheet_id: str = Field(..., description="所属工作表ID")
82
+ org_id: str = Field(..., description="组织ID")
83
+ user_id: str = Field(..., description="用户ID")
84
+ row_key: str = Field(..., description="行索引key")
85
+ position: int = Field(..., description="行位置")
86
+ height: Optional[int] = Field(None, description="行高度")
87
+ hidden: Optional[bool] = Field(None, description="是否隐藏")
88
+ version: int = Field(..., description="版本号")
89
+ created_by_role: str = Field(..., description="创建者角色")
90
+ created_by: str = Field(..., description="创建者ID")
91
+ created_at: datetime = Field(..., description="创建时间")
92
+ updated_at: datetime = Field(..., description="更新时间")
93
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
94
+
95
+ class Config:
96
+ json_encoders = {
97
+ datetime: lambda v: v.isoformat()
98
+ }
99
+
100
+
101
+ class Cell(BaseModel):
102
+ """单元格信息模型"""
103
+ id: str = Field(..., description="单元格ID")
104
+ sheet_id: str = Field(..., description="所属工作表ID")
105
+ column_id: str = Field(..., description="所属列ID")
106
+ row_id: str = Field(..., description="所属行ID")
107
+ org_id: str = Field(..., description="组织ID")
108
+ user_id: str = Field(..., description="用户ID")
109
+ column_key: str = Field(..., description="列索引key")
110
+ row_key: str = Field(..., description="行索引key")
111
+ raw_value: Optional[str] = Field(None, description="原始值")
112
+ formatted_value: Optional[str] = Field(None, description="格式化值")
113
+ formula: Optional[str] = Field(None, description="公式")
114
+ styles: Optional[Dict[str, Any]] = Field(None, description="样式")
115
+ data_type: Optional[str] = Field(None, description="数据类型")
116
+ version: int = Field(..., description="版本号")
117
+ created_by_role: str = Field(..., description="创建者角色")
118
+ created_by: str = Field(..., description="创建者ID")
119
+ created_at: datetime = Field(..., description="创建时间")
120
+ updated_at: datetime = Field(..., description="更新时间")
121
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
122
+
123
+ class Config:
124
+ json_encoders = {
125
+ datetime: lambda v: v.isoformat()
126
+ }
127
+
128
+
129
+ class MergedCell(BaseModel):
130
+ """合并单元格信息模型"""
131
+ id: str = Field(..., description="合并单元格ID")
132
+ sheet_id: str = Field(..., description="所属工作表ID")
133
+ org_id: str = Field(..., description="组织ID")
134
+ user_id: str = Field(..., description="用户ID")
135
+ start_column_id: str = Field(..., description="起始列ID")
136
+ end_column_id: str = Field(..., description="结束列ID")
137
+ start_row_id: str = Field(..., description="起始行ID")
138
+ end_row_id: str = Field(..., description="结束行ID")
139
+ created_at: datetime = Field(..., description="创建时间")
140
+ updated_at: datetime = Field(..., description="更新时间")
141
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
142
+
143
+ class Config:
144
+ json_encoders = {
145
+ datetime: lambda v: v.isoformat()
146
+ }
147
+
148
+
149
+ class TableView(BaseModel):
150
+ """表格视图信息模型"""
151
+ id: str = Field(..., description="视图ID")
152
+ table_id: str = Field(..., description="所属表格ID")
153
+ sheet_id: str = Field(..., description="所属工作表ID")
154
+ org_id: str = Field(..., description="组织ID")
155
+ user_id: str = Field(..., description="用户ID")
156
+ file_id: str = Field(..., description="关联文件ID")
157
+
158
+ # 视图配置字段
159
+ filter_criteria: Optional[Dict[str, Any]] = Field(None, description="过滤条件(JSON)")
160
+ sort_criteria: Optional[Dict[str, Any]] = Field(None, description="排序条件(JSON)")
161
+ visible_columns: Optional[List[str]] = Field(None, description="可见列(列key数组)")
162
+ group_criteria: Optional[Dict[str, Any]] = Field(None, description="分组条件(JSON)")
163
+
164
+ # 创建者信息
165
+ created_by_role: str = Field(..., description="创建者角色:user-用户;agent-智能体")
166
+ created_by: str = Field(..., description="创建者ID")
167
+
168
+ # 视图基本信息
169
+ view_name: str = Field(..., description="视图名称")
170
+ view_type: str = Field(..., description="视图类型:table-表格视图; gantt-甘特图; calendar-日历视图等")
171
+
172
+ # 视图状态
173
+ is_hidden: bool = Field(False, description="是否隐藏")
174
+ is_default: bool = Field(False, description="是否默认视图")
175
+
176
+ # 扩展配置
177
+ config: Optional[Dict[str, Any]] = Field(None, description="视图扩展配置(JSON)")
178
+
179
+ # 时间戳
180
+ created_at: datetime = Field(..., description="创建时间")
181
+ updated_at: datetime = Field(..., description="更新时间")
182
+ deleted_at: Optional[datetime] = Field(None, description="删除时间")
183
+
184
+ class Config:
185
+ json_encoders = {
186
+ datetime: lambda v: v.isoformat()
187
+ }
188
+
189
+
190
+ # 请求和响应模型
191
+ class CellUpdate(BaseModel):
192
+ """单元格更新模型"""
193
+ column_key: str = Field(..., description="列索引key")
194
+ row_key: str = Field(..., description="行索引key")
195
+ raw_value: Optional[str] = Field(None, description="原始值")
196
+ formula: Optional[str] = Field(None, description="公式")
197
+ styles: Optional[Dict[str, Any]] = Field(None, description="样式")
198
+
199
+
200
+ # 响应模型
201
+ class TableResponse(BaseModel):
202
+ """表格响应模型"""
203
+ table: Table = Field(..., description="表格信息")
204
+
205
+
206
+ class SheetResponse(BaseModel):
207
+ """工作表响应模型"""
208
+ sheet: Sheet = Field(..., description="工作表信息")
209
+
210
+
211
+ class ColumnResponse(BaseModel):
212
+ """列响应模型"""
213
+ column: Optional[Column] = Field(None, description="列信息")
214
+ current_version: Optional[int] = Field(None, description="当前版本号")
215
+ applied_immediately: Optional[bool] = Field(None, description="是否立即应用")
216
+
217
+
218
+ class RowResponse(BaseModel):
219
+ """行响应模型"""
220
+ row: Optional[Row] = Field(None, description="行信息")
221
+ current_version: Optional[int] = Field(None, description="当前版本号")
222
+ applied_immediately: Optional[bool] = Field(None, description="是否立即应用")
223
+ success: Optional[bool] = Field(None, description="操作是否成功")
224
+ error_message: Optional[str] = Field(None, description="错误信息")
225
+ conflict_info: Optional['ConflictInfo'] = Field(None, description="冲突信息")
226
+
227
+
228
+ class CellResponse(BaseModel):
229
+ """单元格响应模型"""
230
+ cell: Optional[Cell] = Field(None, description="单元格信息")
231
+ current_version: Optional[int] = Field(None, description="当前版本号")
232
+ applied_immediately: Optional[bool] = Field(None, description="是否立即应用")
233
+ success: Optional[bool] = Field(None, description="操作是否成功")
234
+ error_message: Optional[str] = Field(None, description="错误信息")
235
+ conflict_info: Optional['ConflictInfo'] = Field(None, description="冲突信息")
236
+
237
+
238
+ class MergedCellResponse(BaseModel):
239
+ """合并单元格响应模型"""
240
+ merged_cell: MergedCell = Field(..., description="合并单元格信息")
241
+
242
+
243
+ class ViewResponse(BaseModel):
244
+ """视图响应模型"""
245
+ view: TableView = Field(..., description="视图信息")
246
+
247
+
248
+ # 列表响应模型
249
+ class ListSheetsResponse(BaseModel):
250
+ """工作表列表响应模型"""
251
+ sheets: List[Sheet] = Field(..., description="工作表列表")
252
+
253
+
254
+ class ListColumnsResponse(BaseModel):
255
+ """列列表响应模型"""
256
+ columns: List[Column] = Field(..., description="列列表")
257
+
258
+
259
+ class ListRowsResponse(BaseModel):
260
+ """行列表响应模型"""
261
+ rows: List[Row] = Field(..., description="行列表")
262
+ total: int = Field(..., description="总数")
263
+
264
+
265
+ class BatchCreateRowsResponse(BaseModel):
266
+ """批量创建行响应模型"""
267
+ rows: List[Row] = Field(..., description="创建的行列表")
268
+
269
+
270
+ class BatchUpdateCellsResponse(BaseModel):
271
+ """批量更新单元格响应模型"""
272
+ cells: List[Cell] = Field(..., description="更新的单元格列表")
273
+
274
+
275
+ class GetCellsByRangeResponse(BaseModel):
276
+ """按范围获取单元格响应模型"""
277
+ cells: List[Cell] = Field(..., description="单元格列表")
278
+
279
+
280
+ class ListMergedCellsResponse(BaseModel):
281
+ """合并单元格列表响应模型"""
282
+ merged_cells: List[MergedCell] = Field(..., description="合并单元格列表")
283
+
284
+
285
+ class ListViewsResponse(BaseModel):
286
+ """视图列表响应模型"""
287
+ views: List[TableView] = Field(..., description="视图列表")
288
+
289
+
290
+ class GetSheetVersionResponse(BaseModel):
291
+ """获取工作表版本响应模型"""
292
+ sheet_id: str = Field(..., description="工作表ID")
293
+ version: int = Field(..., description="当前版本号")
294
+ metadata: Optional[Sheet] = Field(None, description="工作表元数据")
295
+
296
+
297
+ class GetSheetDataResponse(BaseModel):
298
+ """获取工作表数据响应模型"""
299
+ sheet_id: str = Field(..., description="工作表ID")
300
+ version: int = Field(..., description="当前版本号")
301
+ metadata: Optional[Sheet] = Field(None, description="工作表元数据")
302
+ columns: List[Column] = Field(default_factory=list, description="列数据")
303
+ rows: List[Row] = Field(default_factory=list, description="行数据")
304
+ cells: List[Cell] = Field(default_factory=list, description="单元格数据")
305
+ last_updated: Optional[datetime] = Field(None, description="最后更新时间")
306
+
307
+ class Config:
308
+ json_encoders = {
309
+ datetime: lambda v: v.isoformat()
310
+ }
311
+
312
+
313
+ class ConflictInfo(BaseModel):
314
+ """冲突信息模型"""
315
+ has_conflict: bool = Field(..., description="是否有冲突")
316
+ server_version: int = Field(..., description="服务器版本号")
317
+ conflict_type: str = Field(..., description="冲突类型")
318
+ conflicted_columns: List[str] = Field(default_factory=list, description="冲突的列")
319
+ resolution_suggestion: Optional[str] = Field(None, description="解决建议")
320
+
321
+
322
+ class BatchEditSheetResponse(BaseModel):
323
+ """批量编辑工作表响应模型"""
324
+ success: bool = Field(..., description="是否成功")
325
+ batch_id: str = Field(..., description="批次ID")
326
+ current_version: int = Field(..., description="当前版本号")
327
+ results: List[Any] = Field(default_factory=list, description="操作结果")
328
+ error_message: Optional[str] = Field(None, description="错误信息")
329
+ conflict_info: Optional[ConflictInfo] = Field(None, description="冲突信息")
330
+
331
+
332
+ class CloneTableDataResponse(BaseModel):
333
+ """克隆表格数据响应模型"""
334
+ success: bool = Field(..., description="是否成功")
335
+ new_table_id: str = Field(..., description="新创建的表格ID")
336
+ new_file_id: str = Field(..., description="新创建的文件ID")
337
+ sheets_cloned: int = Field(..., description="克隆的工作表数量")
338
+ cells_cloned: int = Field(..., description="克隆的单元格数量")
339
+ error_message: Optional[str] = Field(None, description="错误信息")
340
+ created_at: datetime = Field(..., description="创建时间")
341
+
342
+ class Config:
343
+ json_encoders = {
344
+ datetime: lambda v: v.isoformat()
345
+ }
346
+
347
+
348
+ class ExportTableDataResponse(BaseModel):
349
+ """导出表格数据响应模型"""
350
+ success: bool = Field(..., description="是否成功")
351
+ export_id: str = Field(..., description="导出记录ID")
352
+ file_url: str = Field(..., description="GCS文件URL(内部使用)")
353
+ download_url: str = Field(..., description="下载链接(带签名的临时链接)")
354
+ file_size: int = Field(..., description="文件大小(字节)")
355
+ file_name: str = Field(..., description="导出的文件名")
356
+ format: str = Field(..., description="导出格式") # Changed from 'ExportFormat' to str
357
+ sheets_exported: int = Field(..., description="导出的工作表数量")
358
+ error_message: Optional[str] = Field(None, description="错误信息")
359
+ created_at: datetime = Field(..., description="导出时间")
360
+ expires_at: datetime = Field(..., description="下载链接过期时间")
361
+
362
+ class Config:
363
+ json_encoders = {
364
+ datetime: lambda v: v.isoformat()
365
+ }
366
+
367
+
368
+
369
+
370
+ class TableViewResponse(BaseModel):
371
+ """表格视图响应模型"""
372
+ view: TableView = Field(..., description="视图信息")
373
+
374
+
375
+ class BatchCreateTableViewResult(BaseModel):
376
+ """批量创建表格视图结果模型"""
377
+ success: bool = Field(..., description="是否成功")
378
+ view: Optional[TableView] = Field(None, description="成功时返回创建的视图")
379
+ error_message: Optional[str] = Field(None, description="失败时的错误信息")
380
+ view_name: Optional[str] = Field(None, description="视图名称(用于标识是哪个视图)")
381
+
382
+
383
+ class BatchCreateTableViewsResponse(BaseModel):
384
+ """批量创建表格视图响应模型"""
385
+ results: List[BatchCreateTableViewResult] = Field(default_factory=list, description="批量创建结果")
386
+ success_count: int = Field(..., description="成功创建的数量")
387
+ failed_count: int = Field(..., description="失败的数量")
388
+
389
+
390
+ class ListTableViewsResponse(BaseModel):
391
+ """列出表格视图响应模型"""
392
+ views: List[TableView] = Field(default_factory=list, description="视图列表")
393
+ total_count: int = Field(..., description="总数量")
394
+
395
+
396
+ class ImportTableDataResponse(BaseModel):
397
+ """导入表格数据响应模型"""
398
+ success: bool = Field(..., description="是否成功")
399
+ table_id: str = Field(..., description="导入的表格ID(新建或现有)")
400
+ file_id: Optional[str] = Field(None, description="创建的文件ID(如果创建了新表格)")
401
+ sheets_imported: int = Field(..., description="导入的工作表数量")
402
+ rows_imported: int = Field(..., description="导入的行数")
403
+ cells_imported: int = Field(..., description="导入的单元格数量")
404
+ sheet_results: List[Dict[str, Any]] = Field(default_factory=list, description="每个工作表的导入结果")
405
+ error_message: Optional[str] = Field(None, description="错误信息(如果失败)")
406
+ warnings: List[Dict[str, Any]] = Field(default_factory=list, description="警告信息")
407
+ created_at: datetime = Field(..., description="导入时间")
408
+ processing_time_ms: int = Field(..., description="处理时间(毫秒)")
409
+
410
+ class Config:
411
+ json_encoders = {
412
+ datetime: lambda v: v.isoformat()
413
+ }