tamar-file-hub-client 0.0.1__py3-none-any.whl → 0.0.3__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 +556 -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.3.dist-info/METADATA +2050 -0
  32. tamar_file_hub_client-0.0.3.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.3.dist-info}/WHEEL +0 -0
  36. {tamar_file_hub_client-0.0.1.dist-info → tamar_file_hub_client-0.0.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2256 @@
1
+ """
2
+ 同步 Taple 服务
3
+ """
4
+ import grpc
5
+ import tempfile
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Optional, List, Dict, Any, Union, Callable
9
+ import uuid
10
+
11
+ from .base_taple_service import BaseTapleService
12
+ from ...rpc.sync_client import SyncGrpcClient
13
+ from ...schemas.taple import (
14
+ TableResponse, SheetResponse, ColumnResponse, RowResponse, CellResponse,
15
+ ListSheetsResponse, ConflictInfo, CloneTableDataResponse, ExportTableDataResponse
16
+ )
17
+ from ...enums.export_format import ExportFormat
18
+ from ...errors import ValidationError
19
+ from ...utils.retry import retry_with_backoff, retry_on_lock_conflict
20
+ from ...utils.converter import timestamp_to_datetime
21
+
22
+
23
+ class SyncTapleService(BaseTapleService):
24
+ """同步 Taple 服务"""
25
+
26
+ def __init__(self, client: SyncGrpcClient):
27
+ """
28
+ 初始化 Taple 服务
29
+
30
+ Args:
31
+ client: 同步 gRPC 客户端
32
+ """
33
+ self.client = client
34
+
35
+ # Table operations
36
+ @retry_with_backoff(max_retries=3)
37
+ def create_table(
38
+ self,
39
+ name: str,
40
+ *,
41
+ folder_id: Optional[str] = None,
42
+ description: Optional[str] = None,
43
+ idempotency_key: Optional[str] = None,
44
+ request_id: Optional[str] = None,
45
+ **metadata
46
+ ) -> TableResponse:
47
+ """
48
+ 创建表格
49
+
50
+ Args:
51
+ name: 表格名称
52
+ folder_id: 父文件夹ID(可选,默认为"我的文件夹")
53
+ description: 表格描述
54
+ idempotency_key: 幂等性键(可选)
55
+ **metadata: 额外的元数据
56
+
57
+ Returns:
58
+ 创建的表格信息
59
+ """
60
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
61
+
62
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
63
+
64
+ request = taple_service_pb2.CreateTableRequest(name=name)
65
+
66
+ if folder_id:
67
+ request.folder_id = folder_id
68
+ if description:
69
+ request.description = description
70
+ if idempotency_key is not None:
71
+ request.idempotency_key = idempotency_key
72
+
73
+ response = stub.CreateTable(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
74
+ return TableResponse(table=self._convert_table(response.table))
75
+
76
+ @retry_with_backoff(max_retries=3)
77
+ def get_table(
78
+ self,
79
+ *,
80
+ table_id: Optional[str] = None,
81
+ file_id: Optional[str] = None,
82
+ request_id: Optional[str] = None,
83
+ **metadata
84
+ ) -> TableResponse:
85
+ """
86
+ 获取表格信息
87
+
88
+ Args:
89
+ table_id: 表格ID
90
+ file_id: 文件ID
91
+ request_id: 请求ID(可选,如果不提供则自动生成)
92
+ **metadata: 额外的元数据
93
+
94
+ Returns:
95
+ 表格信息
96
+ """
97
+ if not table_id and not file_id:
98
+ raise ValidationError("table_id 或 file_id 必须提供一个")
99
+
100
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
101
+
102
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
103
+
104
+ request = taple_service_pb2.GetTableRequest()
105
+ if table_id:
106
+ request.table_id = table_id
107
+ if file_id:
108
+ request.file_id = file_id
109
+
110
+ response = stub.GetTable(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
111
+ return TableResponse(table=self._convert_table(response.table))
112
+
113
+ @retry_with_backoff(max_retries=3)
114
+ def update_table(
115
+ self,
116
+ table_id: str,
117
+ *,
118
+ name: Optional[str] = None,
119
+ description: Optional[str] = None,
120
+ idempotency_key: Optional[str] = None,
121
+ request_id: Optional[str] = None,
122
+ **metadata
123
+ ) -> TableResponse:
124
+ """
125
+ 更新表格信息
126
+
127
+ Args:
128
+ table_id: 表格ID
129
+ name: 表格名称
130
+ description: 表格描述
131
+ idempotency_key: 幂等性键(可选)
132
+ request_id: 请求ID(可选,如果不提供则自动生成)
133
+ **metadata: 额外的元数据
134
+
135
+ Returns:
136
+ 更新后的表格信息
137
+ """
138
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
139
+
140
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
141
+
142
+ request = taple_service_pb2.UpdateTableRequest(table_id=table_id)
143
+
144
+ if name is not None:
145
+ request.name = name
146
+ if description is not None:
147
+ request.description = description
148
+ if idempotency_key is not None:
149
+ request.idempotency_key = idempotency_key
150
+
151
+ response = stub.UpdateTable(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
152
+ return TableResponse(table=self._convert_table(response.table))
153
+
154
+ @retry_with_backoff(max_retries=3)
155
+ def delete_table(self, table_id: str, *, idempotency_key: Optional[str] = None, request_id: Optional[str] = None,
156
+ **metadata) -> None:
157
+ """
158
+ 删除表格
159
+
160
+ Args:
161
+ table_id: 表格ID
162
+ idempotency_key: 幂等性键(可选)
163
+ request_id: 请求ID(可选,如果不提供则自动生成)
164
+ **metadata: 额外的元数据
165
+ """
166
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
167
+
168
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
169
+
170
+ request = taple_service_pb2.DeleteTableRequest(table_id=table_id)
171
+
172
+ if idempotency_key is not None:
173
+ request.idempotency_key = idempotency_key
174
+
175
+ stub.DeleteTable(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
176
+
177
+ # Sheet operations
178
+ @retry_with_backoff(max_retries=3)
179
+ def create_sheet(
180
+ self,
181
+ table_id: str,
182
+ name: str,
183
+ *,
184
+ description: Optional[str] = None,
185
+ position: Optional[int] = None,
186
+ idempotency_key: Optional[str] = None,
187
+ request_id: Optional[str] = None,
188
+ **metadata
189
+ ) -> SheetResponse:
190
+ """
191
+ 创建工作表
192
+
193
+ Args:
194
+ table_id: 表格ID
195
+ name: 工作表名称
196
+ description: 工作表描述
197
+ position: 工作表位置
198
+ idempotency_key: 幂等性键(可选)
199
+ request_id: 请求ID(可选,如果不提供则自动生成)
200
+ **metadata: 额外的元数据
201
+
202
+ Returns:
203
+ 创建的工作表信息
204
+ """
205
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
206
+
207
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
208
+
209
+ request = taple_service_pb2.CreateSheetRequest(
210
+ table_id=table_id,
211
+ name=name
212
+ )
213
+
214
+ if description:
215
+ request.description = description
216
+ if position is not None:
217
+ request.position = position
218
+ if idempotency_key is not None:
219
+ request.idempotency_key = idempotency_key
220
+
221
+ response = stub.CreateSheet(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
222
+ return SheetResponse(sheet=self._convert_sheet(response.sheet))
223
+
224
+ @retry_with_backoff(max_retries=3)
225
+ def get_sheet(self, sheet_id: str, request_id: Optional[str] = None,
226
+ **metadata) -> SheetResponse:
227
+ """
228
+ 获取工作表信息
229
+
230
+ Args:
231
+ sheet_id: 工作表ID
232
+ request_id: 请求ID(可选,如果不提供则自动生成)
233
+ **metadata: 额外的元数据
234
+
235
+ Returns:
236
+ 工作表信息
237
+ """
238
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
239
+
240
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
241
+
242
+ request = taple_service_pb2.GetSheetRequest(sheet_id=sheet_id)
243
+ response = stub.GetSheet(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
244
+ return SheetResponse(sheet=self._convert_sheet(response.sheet))
245
+
246
+ @retry_with_backoff(max_retries=3)
247
+ def list_sheets(self, table_id: str, request_id: Optional[str] = None,
248
+ **metadata) -> ListSheetsResponse:
249
+ """
250
+ 列出工作表
251
+
252
+ Args:
253
+ table_id: 表格ID
254
+ request_id: 请求ID(可选,如果不提供则自动生成)
255
+ **metadata: 额外的元数据
256
+
257
+ Returns:
258
+ 工作表列表
259
+ """
260
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
261
+
262
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
263
+
264
+ request = taple_service_pb2.ListSheetsRequest(table_id=table_id)
265
+ response = stub.ListSheets(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
266
+
267
+ sheets = [self._convert_sheet(sheet) for sheet in response.sheets]
268
+ return ListSheetsResponse(sheets=sheets)
269
+
270
+ @retry_with_backoff(max_retries=3)
271
+ def update_sheet(
272
+ self,
273
+ sheet_id: str,
274
+ *,
275
+ name: Optional[str] = None,
276
+ description: Optional[str] = None,
277
+ position: Optional[int] = None,
278
+ idempotency_key: Optional[str] = None,
279
+ request_id: Optional[str] = None,
280
+ **metadata
281
+ ) -> SheetResponse:
282
+ """
283
+ 更新工作表
284
+
285
+ Args:
286
+ sheet_id: 工作表ID
287
+ name: 工作表名称
288
+ description: 工作表描述
289
+ position: 工作表位置
290
+ idempotency_key: 幂等性键(可选)
291
+ request_id: 请求ID(可选,如果不提供则自动生成)
292
+ **metadata: 额外的元数据
293
+
294
+ Returns:
295
+ 更新后的工作表信息
296
+ """
297
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
298
+
299
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
300
+
301
+ request = taple_service_pb2.UpdateSheetRequest(sheet_id=sheet_id)
302
+
303
+ if name is not None:
304
+ request.name = name
305
+ if description is not None:
306
+ request.description = description
307
+ if position is not None:
308
+ request.position = position
309
+ if idempotency_key is not None:
310
+ request.idempotency_key = idempotency_key
311
+
312
+ response = stub.UpdateSheet(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
313
+ return SheetResponse(sheet=self._convert_sheet(response.sheet))
314
+
315
+ @retry_with_backoff(max_retries=3)
316
+ def delete_sheet(self, sheet_id: str, *, idempotency_key: Optional[str] = None, request_id: Optional[str] = None,
317
+ **metadata) -> None:
318
+ """
319
+ 删除工作表
320
+
321
+ Args:
322
+ sheet_id: 工作表ID
323
+ idempotency_key: 幂等性键(可选)
324
+ request_id: 请求ID(可选,如果不提供则自动生成)
325
+ **metadata: 额外的元数据
326
+ """
327
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
328
+
329
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
330
+
331
+ request = taple_service_pb2.DeleteSheetRequest(sheet_id=sheet_id)
332
+
333
+ if idempotency_key is not None:
334
+ request.idempotency_key = idempotency_key
335
+
336
+ stub.DeleteSheet(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
337
+
338
+ # Column operations
339
+ @retry_with_backoff(max_retries=3)
340
+ @retry_on_lock_conflict()
341
+ def create_column(
342
+ self,
343
+ sheet_id: str,
344
+ name: str,
345
+ column_type: str = "text",
346
+ *,
347
+ sheet_version: Optional[int] = None,
348
+ client_id: Optional[str] = None,
349
+ position: Optional[int] = None,
350
+ width: Optional[int] = None,
351
+ description: Optional[str] = None,
352
+ properties: Optional[Dict[str, Any]] = None,
353
+ idempotency_key: Optional[str] = None,
354
+ request_id: Optional[str] = None,
355
+ **metadata
356
+ ) -> ColumnResponse:
357
+ """
358
+ 创建列
359
+
360
+ Args:
361
+ sheet_id: 工作表ID
362
+ name: 列名称
363
+ column_type: 列类型
364
+ sheet_version: 版本号(可选,不传则自动获取)
365
+ client_id: 客户端ID(可选,不传则自动生成)
366
+ position: 列位置
367
+ width: 列宽度
368
+ description: 列描述信息
369
+ properties: 列属性
370
+ idempotency_key: 幂等性键(可选)
371
+ request_id: 请求ID(可选,如果不提供则自动生成)
372
+ **metadata: 额外的元数据
373
+
374
+ Returns:
375
+ 创建的列信息
376
+ """
377
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
378
+ import uuid
379
+
380
+ # 如果没有提供sheet_version,自动获取
381
+ if sheet_version is None:
382
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
383
+ sheet_version = version_result.version
384
+
385
+ # 如果没有提供client_id,自动生成
386
+ if client_id is None:
387
+ client_id = str(uuid.uuid4())
388
+
389
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
390
+
391
+ request = taple_service_pb2.CreateColumnRequest(
392
+ sheet_id=sheet_id,
393
+ sheet_version=sheet_version,
394
+ client_id=client_id,
395
+ name=name,
396
+ column_type=column_type
397
+ )
398
+ if position is not None:
399
+ request.position = position
400
+ if width is not None:
401
+ request.width = width
402
+ if description is not None:
403
+ request.description = description
404
+ if properties:
405
+ request.properties.CopyFrom(self._convert_dict_to_struct(properties))
406
+ if idempotency_key is not None:
407
+ request.idempotency_key = idempotency_key
408
+
409
+ response = stub.CreateColumn(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
410
+
411
+ from ...schemas.taple import ColumnResponse
412
+ return ColumnResponse(
413
+ column=self._convert_column(response.column) if response.column else None,
414
+ current_version=response.current_version if hasattr(response, 'current_version') else None,
415
+ applied_immediately=response.applied_immediately if hasattr(response, 'applied_immediately') else None
416
+ )
417
+
418
+
419
+ @retry_with_backoff(max_retries=3)
420
+ @retry_on_lock_conflict()
421
+ def update_column(
422
+ self,
423
+ sheet_id: str,
424
+ column_key: str,
425
+ *,
426
+ sheet_version: Optional[int] = None,
427
+ client_id: Optional[str] = None,
428
+ name: Optional[str] = None,
429
+ column_type: Optional[str] = None,
430
+ width: Optional[int] = None,
431
+ hidden: Optional[bool] = None,
432
+ description: Optional[str] = None,
433
+ properties: Optional[Dict[str, Any]] = None,
434
+ idempotency_key: Optional[str] = None,
435
+ request_id: Optional[str] = None,
436
+ **metadata
437
+ ) -> ColumnResponse:
438
+ """
439
+ 更新列
440
+
441
+ Args:
442
+ sheet_id: 工作表ID
443
+ column_key: 列key
444
+ sheet_version: 版本号(可选,不传则自动获取)
445
+ client_id: 客户端ID(可选,不传则自动生成)
446
+ name: 列名称
447
+ column_type: 列类型
448
+ width: 列宽度
449
+ hidden: 是否隐藏
450
+ description: 列描述信息
451
+ properties: 列属性
452
+ idempotency_key: 幂等性键(可选)
453
+ request_id: 请求ID(可选,如果不提供则自动生成)
454
+ **metadata: 额外的元数据
455
+
456
+ Returns:
457
+ 更新后的列信息
458
+ """
459
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
460
+ import uuid
461
+
462
+ # 如果没有提供sheet_version,自动获取
463
+ if sheet_version is None:
464
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
465
+ sheet_version = version_result.version
466
+
467
+ # 如果没有提供client_id,自动生成
468
+ if client_id is None:
469
+ client_id = str(uuid.uuid4())
470
+
471
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
472
+
473
+ request = taple_service_pb2.UpdateColumnRequest(
474
+ sheet_id=sheet_id,
475
+ sheet_version=sheet_version,
476
+ client_id=client_id,
477
+ column_key=column_key
478
+ )
479
+
480
+ if name is not None:
481
+ request.name = name
482
+ if column_type is not None:
483
+ request.column_type = column_type
484
+ if width is not None:
485
+ request.width = width
486
+ if hidden is not None:
487
+ request.hidden = hidden
488
+ if description is not None:
489
+ request.description = description
490
+ if properties:
491
+ request.properties.CopyFrom(self._convert_dict_to_struct(properties))
492
+ if idempotency_key is not None:
493
+ request.idempotency_key = idempotency_key
494
+
495
+ response = stub.UpdateColumn(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
496
+
497
+ from ...schemas.taple import ColumnResponse
498
+ return ColumnResponse(
499
+ column=self._convert_column(response.column) if response.column else None,
500
+ current_version=response.current_version if hasattr(response, 'current_version') else None,
501
+ applied_immediately=response.applied_immediately if hasattr(response, 'applied_immediately') else None
502
+ )
503
+
504
+ @retry_with_backoff(max_retries=3)
505
+ @retry_on_lock_conflict()
506
+ def delete_column(
507
+ self,
508
+ sheet_id: str,
509
+ column_key: str,
510
+ *,
511
+ sheet_version: Optional[int] = None,
512
+ client_id: Optional[str] = None,
513
+ idempotency_key: Optional[str] = None,
514
+ request_id: Optional[str] = None,
515
+ **metadata
516
+ ) -> None:
517
+ """
518
+ 删除列
519
+
520
+ Args:
521
+ sheet_id: 工作表ID
522
+ column_key: 列key
523
+ sheet_version: 版本号(可选,不传则自动获取)
524
+ client_id: 客户端ID(可选,不传则自动生成)
525
+ idempotency_key: 幂等性键(可选)
526
+ request_id: 请求ID(可选,如果不提供则自动生成)
527
+ **metadata: 额外的元数据
528
+ """
529
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
530
+ import uuid
531
+
532
+ # 如果没有提供sheet_version,自动获取
533
+ if sheet_version is None:
534
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
535
+ sheet_version = version_result.version
536
+
537
+ # 如果没有提供client_id,自动生成
538
+ if client_id is None:
539
+ client_id = str(uuid.uuid4())
540
+
541
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
542
+
543
+ request = taple_service_pb2.DeleteColumnRequest(
544
+ sheet_id=sheet_id,
545
+ sheet_version=sheet_version,
546
+ client_id=client_id,
547
+ column_key=column_key
548
+ )
549
+ if idempotency_key is not None:
550
+ request.idempotency_key = idempotency_key
551
+ stub.DeleteColumn(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
552
+
553
+
554
+ # Row operations
555
+ @retry_with_backoff(max_retries=3)
556
+ @retry_on_lock_conflict()
557
+ def create_row(
558
+ self,
559
+ sheet_id: str,
560
+ *,
561
+ sheet_version: Optional[int] = None,
562
+ client_id: Optional[str] = None,
563
+ position: Optional[int] = None,
564
+ height: Optional[int] = None,
565
+ hidden: Optional[bool] = None,
566
+ idempotency_key: Optional[str] = None,
567
+ request_id: Optional[str] = None,
568
+ **metadata
569
+ ) -> RowResponse:
570
+ """
571
+ 创建行
572
+
573
+ Args:
574
+ sheet_id: 工作表ID
575
+ sheet_version: 版本号(可选,不传则自动获取)
576
+ client_id: 客户端ID(可选,不传则自动生成)
577
+ position: 行位置
578
+ height: 行高度
579
+ hidden: 是否隐藏
580
+ idempotency_key: 幂等性键(可选)
581
+ request_id: 请求ID(可选,如果不提供则自动生成)
582
+ **metadata: 额外的元数据
583
+
584
+ Returns:
585
+ 创建的行信息
586
+ """
587
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
588
+ import uuid
589
+
590
+ # 如果没有提供sheet_version,自动获取
591
+ if sheet_version is None:
592
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
593
+ sheet_version = version_result.version
594
+
595
+ # 如果没有提供client_id,自动生成
596
+ if client_id is None:
597
+ client_id = str(uuid.uuid4())
598
+
599
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
600
+
601
+ request = taple_service_pb2.CreateRowRequest(
602
+ sheet_id=sheet_id,
603
+ sheet_version=sheet_version,
604
+ client_id=client_id
605
+ )
606
+
607
+ if position is not None:
608
+ request.position = position
609
+ if height is not None:
610
+ request.height = height
611
+ if hidden is not None:
612
+ request.hidden = hidden
613
+ if idempotency_key is not None:
614
+ request.idempotency_key = idempotency_key
615
+
616
+ response = stub.CreateRow(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
617
+
618
+ conflict_info = None
619
+ if hasattr(response, 'conflict_info') and response.conflict_info:
620
+ conflict_info = ConflictInfo(
621
+ has_conflict=response.conflict_info.has_conflict,
622
+ server_version=response.conflict_info.server_version,
623
+ conflict_type=response.conflict_info.conflict_type,
624
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
625
+ resolution_suggestion=response.conflict_info.resolution_suggestion
626
+ )
627
+
628
+ return RowResponse(
629
+ row=self._convert_row(response.row) if response.row else None,
630
+ current_version=response.current_version if hasattr(response, 'current_version') else None,
631
+ applied_immediately=response.applied_immediately if hasattr(response, 'applied_immediately') else None,
632
+ success=response.success if hasattr(response, 'success') else None,
633
+ error_message=response.error_message if hasattr(response, 'error_message') else None,
634
+ conflict_info=conflict_info
635
+ )
636
+
637
+
638
+ @retry_with_backoff(max_retries=3)
639
+ @retry_on_lock_conflict()
640
+ def update_row(
641
+ self,
642
+ sheet_id: str,
643
+ row_key: str,
644
+ *,
645
+ sheet_version: Optional[int] = None,
646
+ client_id: Optional[str] = None,
647
+ position: Optional[int] = None,
648
+ height: Optional[int] = None,
649
+ hidden: Optional[bool] = None,
650
+ idempotency_key: Optional[str] = None,
651
+ request_id: Optional[str] = None,
652
+ **metadata
653
+ ) -> RowResponse:
654
+ """
655
+ 更新行
656
+
657
+ Args:
658
+ sheet_id: 工作表ID
659
+ row_key: 行key
660
+ sheet_version: 版本号(可选,不传则自动获取)
661
+ client_id: 客户端ID(可选,不传则自动生成)
662
+ position: 行位置
663
+ height: 行高度
664
+ hidden: 是否隐藏
665
+ idempotency_key: 幂等性键(可选)
666
+ request_id: 请求ID(可选,如果不提供则自动生成)
667
+ **metadata: 额外的元数据
668
+
669
+ Returns:
670
+ 更新后的行信息
671
+ """
672
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
673
+ import uuid
674
+
675
+ # 如果没有提供sheet_version,自动获取
676
+ if sheet_version is None:
677
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
678
+ sheet_version = version_result.version
679
+
680
+ # 如果没有提供client_id,自动生成
681
+ if client_id is None:
682
+ client_id = str(uuid.uuid4())
683
+
684
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
685
+
686
+ request = taple_service_pb2.UpdateRowRequest(
687
+ sheet_id=sheet_id,
688
+ sheet_version=sheet_version,
689
+ client_id=client_id,
690
+ row_key=row_key
691
+ )
692
+
693
+ if position is not None:
694
+ request.position = position
695
+ if height is not None:
696
+ request.height = height
697
+ if hidden is not None:
698
+ request.hidden = hidden
699
+ if idempotency_key is not None:
700
+ request.idempotency_key = idempotency_key
701
+
702
+ response = stub.UpdateRow(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
703
+
704
+ conflict_info = None
705
+ if hasattr(response, 'conflict_info') and response.conflict_info:
706
+ conflict_info = ConflictInfo(
707
+ has_conflict=response.conflict_info.has_conflict,
708
+ server_version=response.conflict_info.server_version,
709
+ conflict_type=response.conflict_info.conflict_type,
710
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
711
+ resolution_suggestion=response.conflict_info.resolution_suggestion
712
+ )
713
+
714
+ return RowResponse(
715
+ row=self._convert_row(response.row) if response.row else None,
716
+ current_version=response.current_version if hasattr(response, 'current_version') else None,
717
+ applied_immediately=response.applied_immediately if hasattr(response, 'applied_immediately') else None,
718
+ success=response.success if hasattr(response, 'success') else None,
719
+ error_message=response.error_message if hasattr(response, 'error_message') else None,
720
+ conflict_info=conflict_info
721
+ )
722
+
723
+ @retry_with_backoff(max_retries=3)
724
+ @retry_on_lock_conflict()
725
+ def delete_row(
726
+ self,
727
+ sheet_id: str,
728
+ row_key: str,
729
+ *,
730
+ sheet_version: Optional[int] = None,
731
+ client_id: Optional[str] = None,
732
+ idempotency_key: Optional[str] = None,
733
+ request_id: Optional[str] = None,
734
+ **metadata
735
+ ) -> None:
736
+ """
737
+ 删除行
738
+
739
+ Args:
740
+ sheet_id: 工作表ID
741
+ row_key: 行key
742
+ sheet_version: 版本号(可选,不传则自动获取)
743
+ client_id: 客户端ID(可选,不传则自动生成)
744
+ idempotency_key: 幂等性键(可选)
745
+ request_id: 请求ID(可选,如果不提供则自动生成)
746
+ **metadata: 额外的元数据
747
+ """
748
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
749
+ import uuid
750
+
751
+ # 如果没有提供sheet_version,自动获取
752
+ if sheet_version is None:
753
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
754
+ sheet_version = version_result.version
755
+
756
+ # 如果没有提供client_id,自动生成
757
+ if client_id is None:
758
+ client_id = str(uuid.uuid4())
759
+
760
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
761
+
762
+ request = taple_service_pb2.DeleteRowRequest(
763
+ sheet_id=sheet_id,
764
+ sheet_version=sheet_version,
765
+ client_id=client_id,
766
+ row_key=row_key
767
+ )
768
+
769
+ if idempotency_key is not None:
770
+ request.idempotency_key = idempotency_key
771
+
772
+ stub.DeleteRow(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
773
+
774
+ # 以下是在 proto 中定义的方法
775
+ @retry_with_backoff(max_retries=3)
776
+ @retry_on_lock_conflict()
777
+ def edit_cell(
778
+ self,
779
+ sheet_id: str,
780
+ column_key: str,
781
+ row_key: str,
782
+ *,
783
+ sheet_version: Optional[int] = None,
784
+ client_id: Optional[str] = None,
785
+ raw_value: Optional[str] = None,
786
+ formatted_value: Optional[str] = None,
787
+ formula: Optional[str] = None,
788
+ data_type: Optional[str] = None,
789
+ styles: Optional[Dict[str, Any]] = None,
790
+ idempotency_key: Optional[str] = None,
791
+ request_id: Optional[str] = None,
792
+ **metadata
793
+ ) -> CellResponse:
794
+ """
795
+ Edit a cell with version control (create or update).
796
+
797
+ Args:
798
+ sheet_id: Sheet ID
799
+ column_key: Column key
800
+ row_key: Row key
801
+ sheet_version: Version for optimistic locking (optional)
802
+ client_id: Client ID (optional)
803
+ raw_value: Cell value
804
+ formatted_value: Formatted value
805
+ formula: Cell formula
806
+ data_type: Data type
807
+ styles: Cell styles
808
+ idempotency_key: 幂等性键(可选)
809
+ request_id: 请求ID(可选,如果不提供则自动生成)
810
+ **metadata: Extra metadata
811
+
812
+ Returns:
813
+ Cell response with updated info
814
+ """
815
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
816
+ import uuid
817
+
818
+ # 如果没有提供sheet_version,自动获取
819
+ if sheet_version is None:
820
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
821
+ sheet_version = version_result.version
822
+
823
+ # 如果没有提供client_id,自动生成
824
+ if client_id is None:
825
+ client_id = str(uuid.uuid4())
826
+
827
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
828
+
829
+ request = taple_service_pb2.EditCellRequest(
830
+ sheet_id=sheet_id,
831
+ column_key=column_key,
832
+ row_key=row_key,
833
+ sheet_version=sheet_version,
834
+ client_id=client_id
835
+ )
836
+
837
+ if raw_value is not None:
838
+ request.raw_value = raw_value
839
+ if formatted_value is not None:
840
+ request.formatted_value = formatted_value
841
+ if formula is not None:
842
+ request.formula = formula
843
+ if data_type is not None:
844
+ request.data_type = data_type
845
+ if styles:
846
+ request.styles.CopyFrom(self._convert_dict_to_struct(styles))
847
+ if idempotency_key is not None:
848
+ request.idempotency_key = idempotency_key
849
+
850
+ response = stub.EditCell(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
851
+
852
+ from ...schemas.taple import CellResponse, ConflictInfo
853
+
854
+ conflict_info = None
855
+ if hasattr(response, 'conflict_info') and response.conflict_info:
856
+ conflict_info = ConflictInfo(
857
+ has_conflict=response.conflict_info.has_conflict,
858
+ server_version=response.conflict_info.server_version,
859
+ conflict_type=response.conflict_info.conflict_type,
860
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
861
+ resolution_suggestion=response.conflict_info.resolution_suggestion
862
+ )
863
+
864
+ return CellResponse(
865
+ cell=self._convert_cell(response.cell) if response.cell else None,
866
+ current_version=response.current_version if hasattr(response, 'current_version') else None,
867
+ applied_immediately=response.applied_immediately if hasattr(response, 'applied_immediately') else None,
868
+ success=response.success if hasattr(response, 'success') else None,
869
+ error_message=response.error_message if hasattr(response, 'error_message') else None,
870
+ conflict_info=conflict_info
871
+ )
872
+
873
+ @retry_with_backoff(max_retries=3)
874
+ @retry_on_lock_conflict()
875
+ def delete_cell(
876
+ self,
877
+ sheet_id: str,
878
+ column_key: str,
879
+ row_key: str,
880
+ *,
881
+ sheet_version: Optional[int] = None,
882
+ client_id: Optional[str] = None,
883
+ idempotency_key: Optional[str] = None,
884
+ request_id: Optional[str] = None,
885
+ **metadata
886
+ ) -> None:
887
+ """
888
+ Delete a cell with version control.
889
+
890
+ Args:
891
+ sheet_id: Sheet ID
892
+ column_key: Column key
893
+ row_key: Row key
894
+ sheet_version: Version for optimistic locking (optional)
895
+ client_id: Client ID (optional)
896
+ idempotency_key: 幂等性键(可选)
897
+ request_id: 请求ID(可选,如果不提供则自动生成)
898
+ **metadata: Extra metadata
899
+ """
900
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
901
+ import uuid
902
+
903
+ # 如果没有提供sheet_version,自动获取
904
+ if sheet_version is None:
905
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
906
+ sheet_version = version_result.version
907
+
908
+ # 如果没有提供client_id,自动生成
909
+ if client_id is None:
910
+ client_id = str(uuid.uuid4())
911
+
912
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
913
+
914
+ request = taple_service_pb2.DeleteCellRequest(
915
+ sheet_id=sheet_id,
916
+ column_key=column_key,
917
+ row_key=row_key,
918
+ sheet_version=sheet_version,
919
+ client_id=client_id
920
+ )
921
+ if idempotency_key is not None:
922
+ request.idempotency_key = idempotency_key
923
+
924
+ stub.DeleteCell(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
925
+
926
+ @retry_with_backoff(max_retries=3)
927
+ @retry_on_lock_conflict()
928
+ def batch_edit_columns(
929
+ self,
930
+ sheet_id: str,
931
+ operations: List[Dict[str, Any]],
932
+ *,
933
+ sheet_version: Optional[int] = None,
934
+ client_id: Optional[str] = None,
935
+ idempotency_key: Optional[str] = None,
936
+ request_id: Optional[str] = None,
937
+ **metadata
938
+ ) -> Any:
939
+ """
940
+ Execute batch column operations.
941
+
942
+ Args:
943
+ sheet_id: Sheet ID
944
+ operations: List of column operations
945
+ sheet_version: Version for optimistic locking (optional)
946
+ client_id: Client ID (optional)
947
+ idempotency_key: 幂等性键(可选)
948
+ request_id: 请求ID(可选,如果不提供则自动生成)
949
+ **metadata: Extra metadata
950
+
951
+ Returns:
952
+ BatchEditColumnsResponse
953
+ """
954
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
955
+ import uuid
956
+
957
+ # 如果没有提供sheet_version,自动获取
958
+ if sheet_version is None:
959
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
960
+ sheet_version = version_result.version
961
+
962
+ # 如果没有提供client_id,自动生成
963
+ if client_id is None:
964
+ client_id = str(uuid.uuid4())
965
+
966
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
967
+
968
+ # Convert operations to proto format
969
+ proto_operations = []
970
+ for op in operations:
971
+ column_op = taple_service_pb2.ColumnOperation()
972
+
973
+ if 'create' in op:
974
+ create_data = taple_service_pb2.CreateColumnData(
975
+ name=op['create']['name']
976
+ )
977
+ if 'column_type' in op['create']:
978
+ create_data.column_type = op['create']['column_type']
979
+ if 'position' in op['create']:
980
+ create_data.position = op['create']['position']
981
+ if 'width' in op['create']:
982
+ create_data.width = op['create']['width']
983
+ if 'properties' in op['create']:
984
+ create_data.properties.CopyFrom(self._convert_dict_to_struct(op['create']['properties']))
985
+ column_op.create.CopyFrom(create_data)
986
+
987
+ elif 'update' in op:
988
+ update_data = taple_service_pb2.UpdateColumnData(
989
+ column_key=op['update']['column_key']
990
+ )
991
+ if 'name' in op['update']:
992
+ update_data.name = op['update']['name']
993
+ if 'column_type' in op['update']:
994
+ update_data.column_type = op['update']['column_type']
995
+ if 'position' in op['update']:
996
+ update_data.position = op['update']['position']
997
+ if 'width' in op['update']:
998
+ update_data.width = op['update']['width']
999
+ if 'hidden' in op['update']:
1000
+ update_data.hidden = op['update']['hidden']
1001
+ if 'description' in op['update']:
1002
+ update_data.description = op['update']['description']
1003
+ if 'properties' in op['update']:
1004
+ update_data.properties.CopyFrom(self._convert_dict_to_struct(op['update']['properties']))
1005
+ column_op.update.CopyFrom(update_data)
1006
+
1007
+ elif 'delete' in op:
1008
+ delete_data = taple_service_pb2.DeleteColumnData(
1009
+ column_key=op['delete']['column_key']
1010
+ )
1011
+ column_op.delete.CopyFrom(delete_data)
1012
+
1013
+ proto_operations.append(column_op)
1014
+
1015
+ request = taple_service_pb2.BatchEditColumnsRequest(
1016
+ sheet_id=sheet_id,
1017
+ sheet_version=sheet_version,
1018
+ client_id=client_id,
1019
+ operations=proto_operations
1020
+ )
1021
+ if idempotency_key is not None:
1022
+ request.idempotency_key = idempotency_key
1023
+
1024
+ response = stub.BatchEditColumns(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1025
+
1026
+ from ...schemas.taple import ConflictInfo
1027
+
1028
+ conflict_info = None
1029
+ if response.conflict_info:
1030
+ conflict_info = ConflictInfo(
1031
+ has_conflict=response.conflict_info.has_conflict,
1032
+ server_version=response.conflict_info.server_version,
1033
+ conflict_type=response.conflict_info.conflict_type,
1034
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
1035
+ resolution_suggestion=response.conflict_info.resolution_suggestion
1036
+ )
1037
+
1038
+ # Return raw response for now, can create proper schema later
1039
+ return {
1040
+ 'success': response.success,
1041
+ 'current_version': response.current_version,
1042
+ 'results': [
1043
+ {
1044
+ 'success': result.success,
1045
+ 'column': self._convert_column(result.column) if result.column else None,
1046
+ 'error_message': result.error_message,
1047
+ 'operation_type': result.operation_type
1048
+ } for result in response.results
1049
+ ],
1050
+ 'error_message': response.error_message,
1051
+ 'conflict_info': conflict_info
1052
+ }
1053
+
1054
+ @retry_with_backoff(max_retries=3)
1055
+ @retry_on_lock_conflict()
1056
+ def batch_edit_rows(
1057
+ self,
1058
+ sheet_id: str,
1059
+ operations: List[Dict[str, Any]],
1060
+ *,
1061
+ sheet_version: Optional[int] = None,
1062
+ client_id: Optional[str] = None,
1063
+ idempotency_key: Optional[str] = None,
1064
+ request_id: Optional[str] = None,
1065
+ **metadata
1066
+ ) -> Any:
1067
+ """
1068
+ Execute batch row operations.
1069
+
1070
+ Args:
1071
+ sheet_id: Sheet ID
1072
+ operations: List of row operations
1073
+ sheet_version: Version for optimistic locking (optional)
1074
+ client_id: Client ID (optional)
1075
+ idempotency_key: 幂等性键(可选)
1076
+ request_id: 请求ID(可选,如果不提供则自动生成)
1077
+ **metadata: Extra metadata
1078
+
1079
+ Returns:
1080
+ BatchEditRowsResponse
1081
+ """
1082
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1083
+ import uuid
1084
+
1085
+ # 如果没有提供sheet_version,自动获取
1086
+ if sheet_version is None:
1087
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
1088
+ sheet_version = version_result.version
1089
+
1090
+ # 如果没有提供client_id,自动生成
1091
+ if client_id is None:
1092
+ client_id = str(uuid.uuid4())
1093
+
1094
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1095
+
1096
+ # Convert operations to proto format
1097
+ proto_operations = []
1098
+ for op in operations:
1099
+ row_op = taple_service_pb2.RowOperation()
1100
+
1101
+ if 'create' in op:
1102
+ # Try setting fields directly on the nested message
1103
+ if 'position' in op['create']:
1104
+ row_op.create.position = op['create']['position']
1105
+ if 'height' in op['create']:
1106
+ row_op.create.height = op['create']['height']
1107
+
1108
+ elif 'update' in op:
1109
+ # Set fields directly on the nested message
1110
+ row_op.update.row_key = op['update']['row_key']
1111
+ if 'position' in op['update']:
1112
+ row_op.update.position = op['update']['position']
1113
+ if 'height' in op['update']:
1114
+ row_op.update.height = op['update']['height']
1115
+ if 'hidden' in op['update']:
1116
+ row_op.update.hidden = op['update']['hidden']
1117
+
1118
+ elif 'delete' in op:
1119
+ # Set fields directly on the nested message
1120
+ row_op.delete.row_key = op['delete']['row_key']
1121
+
1122
+ proto_operations.append(row_op)
1123
+
1124
+ request = taple_service_pb2.BatchEditRowsRequest(
1125
+ sheet_id=sheet_id,
1126
+ sheet_version=sheet_version,
1127
+ client_id=client_id,
1128
+ operations=proto_operations
1129
+ )
1130
+ if idempotency_key is not None:
1131
+ request.idempotency_key = idempotency_key
1132
+
1133
+ try:
1134
+ response = stub.BatchEditRows(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1135
+ except Exception as e:
1136
+ # 如果发生异常,返回错误信息
1137
+ return {
1138
+ 'success': False,
1139
+ 'error_message': str(e),
1140
+ 'current_version': sheet_version,
1141
+ 'results': [],
1142
+ 'conflict_info': None
1143
+ }
1144
+
1145
+ from ...schemas.taple import ConflictInfo
1146
+
1147
+ conflict_info = None
1148
+ if response.conflict_info:
1149
+ conflict_info = ConflictInfo(
1150
+ has_conflict=response.conflict_info.has_conflict,
1151
+ server_version=response.conflict_info.server_version,
1152
+ conflict_type=response.conflict_info.conflict_type,
1153
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
1154
+ resolution_suggestion=response.conflict_info.resolution_suggestion
1155
+ )
1156
+
1157
+ # Return raw response for now, can create proper schema later
1158
+ return {
1159
+ 'success': response.success if hasattr(response, 'success') else False,
1160
+ 'current_version': response.current_version if hasattr(response, 'current_version') else sheet_version,
1161
+ 'results': [
1162
+ {
1163
+ 'success': result.success if hasattr(result, 'success') else False,
1164
+ 'row': self._convert_row(result.row) if hasattr(result, 'row') and result.row else None,
1165
+ 'error_message': result.error_message if hasattr(result, 'error_message') else None,
1166
+ 'operation_type': result.operation_type if hasattr(result, 'operation_type') else None
1167
+ } for result in (response.results if hasattr(response, 'results') else [])
1168
+ ],
1169
+ 'error_message': response.error_message if hasattr(response, 'error_message') else None,
1170
+ 'conflict_info': conflict_info
1171
+ }
1172
+
1173
+ @retry_with_backoff(max_retries=3)
1174
+ @retry_on_lock_conflict()
1175
+ def batch_edit_cells(
1176
+ self,
1177
+ sheet_id: str,
1178
+ operations: List[Dict[str, Any]],
1179
+ *,
1180
+ sheet_version: Optional[int] = None,
1181
+ client_id: Optional[str] = None,
1182
+ idempotency_key: Optional[str] = None,
1183
+ request_id: Optional[str] = None,
1184
+ **metadata
1185
+ ) -> Any:
1186
+ """
1187
+ Execute batch cell operations.
1188
+
1189
+ Args:
1190
+ sheet_id: Sheet ID
1191
+ operations: List of cell operations
1192
+ sheet_version: Version for optimistic locking (optional)
1193
+ client_id: Client ID (optional)
1194
+ idempotency_key: 幂等性键(可选)
1195
+ request_id: 请求ID(可选,如果不提供则自动生成)
1196
+ **metadata: Extra metadata
1197
+
1198
+ Returns:
1199
+ BatchEditCellsResponse
1200
+ """
1201
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1202
+ import uuid
1203
+
1204
+ # 如果没有提供sheet_version,自动获取
1205
+ if sheet_version is None:
1206
+ version_result = self.get_sheet_version(sheet_id=sheet_id, **metadata)
1207
+ sheet_version = version_result.version
1208
+
1209
+ # 如果没有提供client_id,自动生成
1210
+ if client_id is None:
1211
+ client_id = str(uuid.uuid4())
1212
+
1213
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1214
+
1215
+ # Convert operations to proto format
1216
+ proto_operations = []
1217
+ for op in operations:
1218
+ cell_op = taple_service_pb2.CellOperation()
1219
+
1220
+ if 'edit' in op:
1221
+ edit_data = taple_service_pb2.EditCellData(
1222
+ column_key=op['edit']['column_key'],
1223
+ row_key=op['edit']['row_key']
1224
+ )
1225
+ if 'raw_value' in op['edit']:
1226
+ edit_data.raw_value = op['edit']['raw_value']
1227
+ if 'formatted_value' in op['edit']:
1228
+ edit_data.formatted_value = op['edit']['formatted_value']
1229
+ if 'formula' in op['edit']:
1230
+ edit_data.formula = op['edit']['formula']
1231
+ if 'data_type' in op['edit']:
1232
+ edit_data.data_type = op['edit']['data_type']
1233
+ if 'styles' in op['edit']:
1234
+ edit_data.styles.CopyFrom(self._convert_dict_to_struct(op['edit']['styles']))
1235
+ cell_op.edit.CopyFrom(edit_data)
1236
+
1237
+ elif 'clear' in op:
1238
+ clear_data = taple_service_pb2.ClearCellData(
1239
+ column_key=op['clear']['column_key'],
1240
+ row_key=op['clear']['row_key']
1241
+ )
1242
+ cell_op.clear.CopyFrom(clear_data)
1243
+
1244
+ elif 'delete' in op:
1245
+ delete_data = taple_service_pb2.DeleteCellData(
1246
+ column_key=op['delete']['column_key'],
1247
+ row_key=op['delete']['row_key']
1248
+ )
1249
+ cell_op.delete.CopyFrom(delete_data)
1250
+
1251
+ proto_operations.append(cell_op)
1252
+
1253
+ request = taple_service_pb2.BatchEditCellsRequest(
1254
+ sheet_id=sheet_id,
1255
+ sheet_version=sheet_version,
1256
+ client_id=client_id,
1257
+ operations=proto_operations
1258
+ )
1259
+ if idempotency_key is not None:
1260
+ request.idempotency_key = idempotency_key
1261
+
1262
+ response = stub.BatchEditCells(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1263
+
1264
+ from ...schemas.taple import ConflictInfo
1265
+
1266
+ conflict_info = None
1267
+ if response.conflict_info:
1268
+ conflict_info = ConflictInfo(
1269
+ has_conflict=response.conflict_info.has_conflict,
1270
+ server_version=response.conflict_info.server_version,
1271
+ conflict_type=response.conflict_info.conflict_type,
1272
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
1273
+ resolution_suggestion=response.conflict_info.resolution_suggestion
1274
+ )
1275
+
1276
+ # Return raw response for now, can create proper schema later
1277
+ return {
1278
+ 'success': response.success,
1279
+ 'current_version': response.current_version,
1280
+ 'results': [
1281
+ {
1282
+ 'success': result.success,
1283
+ 'cell': self._convert_cell(result.cell) if result.cell else None,
1284
+ 'error_message': result.error_message,
1285
+ 'operation_type': result.operation_type
1286
+ } for result in response.results
1287
+ ],
1288
+ 'error_message': response.error_message,
1289
+ 'conflict_info': conflict_info
1290
+ }
1291
+
1292
+ @retry_with_backoff(max_retries=3)
1293
+ def get_column_data(
1294
+ self,
1295
+ sheet_id: str,
1296
+ column_key: str,
1297
+ request_id: Optional[str] = None,
1298
+ **metadata
1299
+ ) -> Any:
1300
+ """
1301
+ Get column data including all cells in the column.
1302
+
1303
+ Args:
1304
+ sheet_id: Sheet ID
1305
+ column_key: Column key
1306
+ request_id: 请求ID(可选,如果不提供则自动生成)
1307
+ **metadata: Extra metadata
1308
+
1309
+ Returns:
1310
+ ColumnDataResponse with column info and cells
1311
+ """
1312
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1313
+
1314
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1315
+
1316
+ request = taple_service_pb2.GetColumnDataRequest(
1317
+ sheet_id=sheet_id,
1318
+ column_key=column_key
1319
+ )
1320
+
1321
+ response = stub.GetColumnData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1322
+
1323
+ # Return raw response for now, can create proper schema later
1324
+ return {
1325
+ 'column': self._convert_column(response.column) if response.column else None,
1326
+ 'cells': [self._convert_cell(cell) for cell in response.cells]
1327
+ }
1328
+
1329
+ @retry_with_backoff(max_retries=3)
1330
+ def get_row_data(
1331
+ self,
1332
+ sheet_id: str,
1333
+ row_key: str,
1334
+ request_id: Optional[str] = None,
1335
+ **metadata
1336
+ ) -> Any:
1337
+ """
1338
+ Get row data including all cells in the row.
1339
+
1340
+ Args:
1341
+ sheet_id: Sheet ID
1342
+ row_key: Row key
1343
+ request_id: 请求ID(可选,如果不提供则自动生成)
1344
+ **metadata: Extra metadata
1345
+
1346
+ Returns:
1347
+ RowDataResponse with row info and cells
1348
+ """
1349
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1350
+
1351
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1352
+
1353
+ request = taple_service_pb2.GetRowDataRequest(
1354
+ sheet_id=sheet_id,
1355
+ row_key=row_key
1356
+ )
1357
+
1358
+ response = stub.GetRowData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1359
+
1360
+ # Return raw response for now, can create proper schema later
1361
+ return {
1362
+ 'row': self._convert_row(response.row) if response.row else None,
1363
+ 'cells': [self._convert_cell(cell) for cell in response.cells]
1364
+ }
1365
+
1366
+ @retry_with_backoff(max_retries=3)
1367
+ def get_cell_data(
1368
+ self,
1369
+ sheet_id: str,
1370
+ column_key: str,
1371
+ row_key: str,
1372
+ request_id: Optional[str] = None,
1373
+ **metadata
1374
+ ) -> Any:
1375
+ """
1376
+ Get cell data.
1377
+
1378
+ Args:
1379
+ sheet_id: Sheet ID
1380
+ column_key: Column key
1381
+ row_key: Row key
1382
+ request_id: 请求ID(可选,如果不提供则自动生成)
1383
+ **metadata: Extra metadata
1384
+
1385
+ Returns:
1386
+ CellDataResponse with cell info
1387
+ """
1388
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1389
+
1390
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1391
+
1392
+ request = taple_service_pb2.GetCellDataRequest(
1393
+ sheet_id=sheet_id,
1394
+ column_key=column_key,
1395
+ row_key=row_key
1396
+ )
1397
+
1398
+ response = stub.GetCellData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1399
+
1400
+ # Return raw response for now, can create proper schema later
1401
+ return {
1402
+ 'cell': self._convert_cell(response.cell) if response.cell else None
1403
+ }
1404
+
1405
+ @retry_with_backoff(max_retries=3)
1406
+ def get_sheet_version(
1407
+ self,
1408
+ sheet_id: str,
1409
+ request_id: Optional[str] = None,
1410
+ **metadata
1411
+ ) -> Any:
1412
+ """
1413
+ Get sheet version (lightweight).
1414
+
1415
+ Args:
1416
+ sheet_id: Sheet ID
1417
+ request_id: 请求ID(可选,如果不提供则自动生成)
1418
+ **metadata: Extra metadata
1419
+
1420
+ Returns:
1421
+ GetSheetVersionResponse
1422
+ """
1423
+ if not sheet_id:
1424
+ raise ValidationError("sheet_id 不能为空")
1425
+
1426
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1427
+
1428
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1429
+
1430
+ request = taple_service_pb2.GetSheetVersionRequest(sheet_id=sheet_id)
1431
+ response = stub.GetSheetVersion(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1432
+
1433
+ from ...schemas.taple import GetSheetVersionResponse
1434
+ return GetSheetVersionResponse(
1435
+ sheet_id=response.sheet_id,
1436
+ version=response.version,
1437
+ metadata=self._convert_sheet(response.metadata) if response.metadata else None
1438
+ )
1439
+
1440
+ @retry_with_backoff(max_retries=3)
1441
+ def get_sheet_data(
1442
+ self,
1443
+ sheet_id: str,
1444
+ version: Optional[int] = None,
1445
+ request_id: Optional[str] = None,
1446
+ **metadata
1447
+ ) -> Any:
1448
+ """
1449
+ Get complete sheet data.
1450
+
1451
+ Args:
1452
+ sheet_id: Sheet ID
1453
+ version: Optional version to get changes since
1454
+ request_id: 请求ID(可选,如果不提供则自动生成)
1455
+ **metadata: Extra metadata
1456
+
1457
+ Returns:
1458
+ GetSheetDataResponse
1459
+ """
1460
+ if not sheet_id:
1461
+ raise ValidationError("sheet_id 不能为空")
1462
+
1463
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1464
+
1465
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1466
+
1467
+ request = taple_service_pb2.GetSheetDataRequest(sheet_id=sheet_id)
1468
+ if version is not None:
1469
+ request.version = version
1470
+
1471
+ response = stub.GetSheetData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1472
+
1473
+ from ...schemas.taple import GetSheetDataResponse
1474
+ return GetSheetDataResponse(
1475
+ sheet_id=response.sheet_id,
1476
+ version=response.version,
1477
+ metadata=self._convert_sheet(response.metadata) if response.metadata else None,
1478
+ columns=[self._convert_column(col) for col in response.columns] if response.columns else [],
1479
+ rows=[self._convert_row(row) for row in response.rows] if response.rows else [],
1480
+ cells=[self._convert_cell(cell) for cell in response.cells] if response.cells else [],
1481
+ last_updated=timestamp_to_datetime(response.last_updated) if response.last_updated else None
1482
+ )
1483
+
1484
+ @retry_with_backoff(max_retries=3)
1485
+ @retry_on_lock_conflict()
1486
+ def batch_edit_sheet(
1487
+ self,
1488
+ sheet_id: str,
1489
+ operations: List[Any],
1490
+ sheet_version: int,
1491
+ client_id: str,
1492
+ *,
1493
+ idempotency_key: Optional[str] = None,
1494
+ request_id: Optional[str] = None,
1495
+ **metadata
1496
+ ) -> Any:
1497
+ """
1498
+ Execute batch operations on a sheet.
1499
+
1500
+ Args:
1501
+ sheet_id: Sheet ID
1502
+ operations: List of operations
1503
+ sheet_version: Current version for optimistic locking
1504
+ client_id: Client ID
1505
+ idempotency_key: 幂等性键(可选)
1506
+ request_id: 请求ID(可选,如果不提供则自动生成)
1507
+ **metadata: Extra metadata
1508
+
1509
+ Returns:
1510
+ BatchEditSheetResponse
1511
+ """
1512
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1513
+
1514
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1515
+
1516
+ request = taple_service_pb2.BatchEditSheetRequest(
1517
+ sheet_id=sheet_id,
1518
+ operations=operations,
1519
+ sheet_version=sheet_version,
1520
+ client_id=client_id
1521
+ )
1522
+
1523
+ if idempotency_key is not None:
1524
+ request.idempotency_key = idempotency_key
1525
+
1526
+ response = stub.BatchEditSheet(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1527
+
1528
+ from ...schemas.taple import BatchEditSheetResponse, ConflictInfo
1529
+
1530
+ conflict_info = None
1531
+ if response.conflict_info:
1532
+ conflict_info = ConflictInfo(
1533
+ has_conflict=response.conflict_info.has_conflict,
1534
+ server_version=response.conflict_info.server_version,
1535
+ conflict_type=response.conflict_info.conflict_type,
1536
+ conflicted_columns=list(response.conflict_info.conflicted_columns),
1537
+ resolution_suggestion=response.conflict_info.resolution_suggestion
1538
+ )
1539
+
1540
+ return BatchEditSheetResponse(
1541
+ success=response.success,
1542
+ batch_id=response.batch_id,
1543
+ current_version=response.current_version,
1544
+ results=list(response.results),
1545
+ error_message=response.error_message,
1546
+ conflict_info=conflict_info
1547
+ )
1548
+
1549
+ @retry_with_backoff(max_retries=3)
1550
+ def clone_table_data(
1551
+ self,
1552
+ source_table_id: str,
1553
+ target_org_id: str,
1554
+ target_user_id: str,
1555
+ *,
1556
+ target_folder_id: Optional[str] = None,
1557
+ new_table_name: Optional[str] = None,
1558
+ include_views: bool = False,
1559
+ idempotency_key: Optional[str] = None,
1560
+ request_id: Optional[str] = None,
1561
+ **metadata
1562
+ ) -> 'CloneTableDataResponse':
1563
+ """
1564
+ Clone table data to another organization.
1565
+
1566
+ Args:
1567
+ source_table_id: Source table ID
1568
+ target_org_id: Target organization ID
1569
+ target_user_id: Target user ID
1570
+ target_folder_id: Target folder ID (optional)
1571
+ new_table_name: New table name (optional, defaults to original name + Copy)
1572
+ include_views: Whether to include view data (default: False)
1573
+ idempotency_key: Idempotency key (optional)
1574
+ request_id: 请求ID(可选,如果不提供则自动生成)
1575
+ **metadata: Extra metadata
1576
+
1577
+ Returns:
1578
+ CloneTableDataResponse with clone operation result
1579
+ """
1580
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1581
+ from ...schemas.taple import CloneTableDataResponse
1582
+
1583
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1584
+
1585
+ request = taple_service_pb2.CloneTableDataRequest(
1586
+ source_table_id=source_table_id,
1587
+ target_org_id=target_org_id,
1588
+ target_user_id=target_user_id
1589
+ )
1590
+
1591
+ if target_folder_id:
1592
+ request.target_folder_id = target_folder_id
1593
+ if new_table_name:
1594
+ request.new_table_name = new_table_name
1595
+ if include_views is not None:
1596
+ request.include_views = include_views
1597
+ if idempotency_key:
1598
+ request.idempotency_key = idempotency_key
1599
+
1600
+ response = stub.CloneTableData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1601
+
1602
+ return CloneTableDataResponse(
1603
+ success=response.success,
1604
+ new_table_id=response.new_table_id,
1605
+ new_file_id=response.new_file_id,
1606
+ sheets_cloned=response.sheets_cloned,
1607
+ cells_cloned=response.cells_cloned,
1608
+ error_message=response.error_message if response.error_message else None,
1609
+ created_at=timestamp_to_datetime(response.created_at)
1610
+ )
1611
+
1612
+ @retry_with_backoff(max_retries=3)
1613
+ def export_table_data(
1614
+ self,
1615
+ table_id: str,
1616
+ format: 'ExportFormat',
1617
+ *,
1618
+ sheet_ids: Optional[List[str]] = None,
1619
+ options: Optional[Dict[str, Any]] = None,
1620
+ idempotency_key: Optional[str] = None,
1621
+ request_id: Optional[str] = None,
1622
+ **metadata
1623
+ ) -> 'ExportTableDataResponse':
1624
+ """
1625
+ Export table data to file.
1626
+
1627
+ Args:
1628
+ table_id: Table ID to export
1629
+ format: Export format (EXCEL, CSV, JSON)
1630
+ sheet_ids: List of sheet IDs to export (optional, empty means all)
1631
+ options: Export options dict
1632
+ idempotency_key: Idempotency key (optional)
1633
+ request_id: 请求ID(可选,如果不提供则自动生成)
1634
+ **metadata: Extra metadata
1635
+
1636
+ Returns:
1637
+ ExportTableDataResponse with export result
1638
+ """
1639
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1640
+ from ...schemas.taple import ExportTableDataResponse
1641
+ from google.protobuf import struct_pb2
1642
+
1643
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1644
+
1645
+ # Convert format enum
1646
+ format_map = {
1647
+ ExportFormat.XLSX: taple_service_pb2.EXPORT_FORMAT_EXCEL,
1648
+ ExportFormat.CSV: taple_service_pb2.EXPORT_FORMAT_CSV,
1649
+ ExportFormat.JSON: taple_service_pb2.EXPORT_FORMAT_JSON,
1650
+ }
1651
+
1652
+ request = taple_service_pb2.ExportTableDataRequest(
1653
+ table_id=table_id,
1654
+ format=format_map.get(format, taple_service_pb2.EXPORT_FORMAT_UNSPECIFIED)
1655
+ )
1656
+
1657
+ if sheet_ids:
1658
+ request.sheet_ids.extend(sheet_ids)
1659
+
1660
+ if options:
1661
+ # Create ExportOptions
1662
+ export_options = taple_service_pb2.ExportOptions()
1663
+ if 'include_formulas' in options:
1664
+ export_options.include_formulas = options['include_formulas']
1665
+ if 'include_styles' in options:
1666
+ export_options.include_styles = options['include_styles']
1667
+ if 'include_hidden_sheets' in options:
1668
+ export_options.include_hidden_sheets = options['include_hidden_sheets']
1669
+ if 'include_hidden_rows_cols' in options:
1670
+ export_options.include_hidden_rows_cols = options['include_hidden_rows_cols']
1671
+ if 'date_format' in options:
1672
+ export_options.date_format = options['date_format']
1673
+ if 'csv_delimiter' in options:
1674
+ export_options.csv_delimiter = options['csv_delimiter']
1675
+ if 'csv_encoding' in options:
1676
+ export_options.csv_encoding = options['csv_encoding']
1677
+ request.options.CopyFrom(export_options)
1678
+
1679
+ if idempotency_key:
1680
+ request.idempotency_key = idempotency_key
1681
+
1682
+ response = stub.ExportTableData(request, metadata=self.client.build_metadata(request_id=request_id, **metadata))
1683
+
1684
+ return ExportTableDataResponse(
1685
+ success=response.success,
1686
+ export_id=response.export_id,
1687
+ file_url=response.file_url,
1688
+ download_url=response.download_url,
1689
+ file_size=response.file_size,
1690
+ file_name=response.file_name,
1691
+ format=format.value if hasattr(format, 'value') else str(format), # Convert enum to string
1692
+ sheets_exported=response.sheets_exported,
1693
+ error_message=response.error_message if response.error_message else None,
1694
+ created_at=timestamp_to_datetime(response.created_at),
1695
+ expires_at=timestamp_to_datetime(response.expires_at)
1696
+ )
1697
+
1698
+ @retry_with_backoff(max_retries=3)
1699
+ def import_table_data(
1700
+ self,
1701
+ file_id: str,
1702
+ *,
1703
+ target_table_id: Optional[str] = None,
1704
+ table_name: Optional[str] = None,
1705
+ folder_id: Optional[str] = None,
1706
+ import_mode: str = "APPEND",
1707
+ skip_first_row: bool = True,
1708
+ auto_detect_types: bool = True,
1709
+ clear_existing_data: bool = False,
1710
+ column_mapping: Optional[Dict[str, str]] = None,
1711
+ date_format: str = "YYYY-MM-DD",
1712
+ csv_delimiter: str = ",",
1713
+ csv_encoding: str = "UTF-8",
1714
+ max_rows: int = 0,
1715
+ idempotency_key: Optional[str] = None,
1716
+ request_id: Optional[str] = None,
1717
+ **metadata
1718
+ ) -> 'ImportTableDataResponse':
1719
+ """
1720
+ 导入文件数据到表格
1721
+
1722
+ Args:
1723
+ file_id: 要导入的文件ID
1724
+ target_table_id: 目标表格ID(可选,不提供则创建新表格)
1725
+ table_name: 表格名称(仅在创建新表格时使用)
1726
+ folder_id: 文件夹ID(仅在创建新表格时使用)
1727
+ import_mode: 导入模式(APPEND/REPLACE/MERGE)
1728
+ skip_first_row: 是否跳过第一行(标题行)
1729
+ auto_detect_types: 是否自动检测列类型
1730
+ clear_existing_data: 是否清空现有数据(仅在导入到现有表格时)
1731
+ column_mapping: 列映射(源列名 -> 目标列名)
1732
+ date_format: 日期格式
1733
+ csv_delimiter: CSV分隔符
1734
+ csv_encoding: CSV编码
1735
+ max_rows: 最大导入行数限制(0表示无限制)
1736
+ idempotency_key: 幂等性键
1737
+ request_id: 请求ID(可选,如果不提供则自动生成)
1738
+ **metadata: 额外的元数据
1739
+
1740
+ Returns:
1741
+ ImportTableDataResponse
1742
+ """
1743
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1744
+ from ...schemas.taple import ImportTableDataResponse
1745
+
1746
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1747
+
1748
+ # 构建导入选项
1749
+ import_options = taple_service_pb2.ImportOptions(
1750
+ import_mode=self._get_import_mode_enum(import_mode),
1751
+ skip_first_row=skip_first_row,
1752
+ auto_detect_types=auto_detect_types,
1753
+ clear_existing_data=clear_existing_data,
1754
+ date_format=date_format,
1755
+ csv_delimiter=csv_delimiter,
1756
+ csv_encoding=csv_encoding,
1757
+ max_rows=max_rows
1758
+ )
1759
+
1760
+ # 添加列映射
1761
+ if column_mapping:
1762
+ for source_col, target_col in column_mapping.items():
1763
+ import_options.column_mapping[source_col] = target_col
1764
+
1765
+ # 构建请求
1766
+ request = taple_service_pb2.ImportTableDataRequest(
1767
+ file_id=file_id,
1768
+ options=import_options
1769
+ )
1770
+
1771
+ if target_table_id:
1772
+ request.target_table_id = target_table_id
1773
+ if folder_id:
1774
+ request.folder_id = folder_id
1775
+ if table_name:
1776
+ request.table_name = table_name
1777
+ if idempotency_key:
1778
+ request.idempotency_key = idempotency_key
1779
+
1780
+ # 调用RPC
1781
+ response = stub.ImportTableData(
1782
+ request,
1783
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1784
+ )
1785
+
1786
+ # 转换响应
1787
+ return ImportTableDataResponse(
1788
+ success=response.success,
1789
+ table_id=response.table_id,
1790
+ file_id=response.file_id if response.file_id else None,
1791
+ sheets_imported=response.sheets_imported,
1792
+ rows_imported=response.rows_imported,
1793
+ cells_imported=response.cells_imported,
1794
+ sheet_results=[
1795
+ {
1796
+ 'sheet_name': result.sheet_name,
1797
+ 'sheet_id': result.sheet_id,
1798
+ 'rows_imported': result.rows_imported,
1799
+ 'cells_imported': result.cells_imported,
1800
+ 'success': result.success,
1801
+ 'error_message': result.error_message if result.error_message else None
1802
+ }
1803
+ for result in response.sheet_results
1804
+ ],
1805
+ error_message=response.error_message if response.error_message else None,
1806
+ warnings=[
1807
+ {
1808
+ 'type': warning.type,
1809
+ 'message': warning.message,
1810
+ 'sheet_name': warning.sheet_name if warning.sheet_name else None,
1811
+ 'row_number': warning.row_number if warning.row_number else None,
1812
+ 'column_name': warning.column_name if warning.column_name else None
1813
+ }
1814
+ for warning in response.warnings
1815
+ ],
1816
+ created_at=timestamp_to_datetime(response.created_at),
1817
+ processing_time_ms=response.processing_time_ms
1818
+ )
1819
+
1820
+ def _get_import_mode_enum(self, mode: str) -> int:
1821
+ """将字符串导入模式转换为枚举值"""
1822
+ from ...rpc.gen import taple_service_pb2
1823
+
1824
+ mode_map = {
1825
+ "APPEND": taple_service_pb2.IMPORT_MODE_APPEND,
1826
+ "REPLACE": taple_service_pb2.IMPORT_MODE_REPLACE,
1827
+ "MERGE": taple_service_pb2.IMPORT_MODE_MERGE
1828
+ }
1829
+
1830
+ return mode_map.get(mode.upper(), taple_service_pb2.IMPORT_MODE_APPEND)
1831
+
1832
+ # Table view operations
1833
+ @retry_with_backoff(max_retries=3)
1834
+ def create_table_view(
1835
+ self,
1836
+ sheet_id: str,
1837
+ name: str,
1838
+ view_type: str,
1839
+ *,
1840
+ filter_criteria: Optional[Dict[str, Any]] = None,
1841
+ sort_criteria: Optional[Dict[str, Any]] = None,
1842
+ visible_columns: Optional[List[str]] = None,
1843
+ group_criteria: Optional[Dict[str, Any]] = None,
1844
+ is_hidden: bool = False,
1845
+ is_default: bool = False,
1846
+ config: Optional[Dict[str, Any]] = None,
1847
+ idempotency_key: Optional[str] = None,
1848
+ request_id: Optional[str] = None,
1849
+ **metadata
1850
+ ) -> 'TableViewResponse':
1851
+ """
1852
+ 创建表格视图
1853
+
1854
+ Args:
1855
+ sheet_id: 所属工作表ID
1856
+ name: 视图名称
1857
+ view_type: 视图类型(table/gantt/calendar/kanban/gallery等)
1858
+ filter_criteria: 过滤条件(可选)
1859
+ sort_criteria: 排序条件(可选)
1860
+ visible_columns: 可见列列表(可选)
1861
+ group_criteria: 分组条件(可选)
1862
+ is_hidden: 是否隐藏(默认False)
1863
+ is_default: 是否默认视图(默认False)
1864
+ config: 扩展配置(可选)
1865
+ idempotency_key: 幂等性键(可选)
1866
+ request_id: 请求ID(可选,如果不提供则自动生成)
1867
+ **metadata: 额外的元数据
1868
+
1869
+ Returns:
1870
+ TableViewResponse
1871
+ """
1872
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1873
+ from ...schemas.taple import TableViewResponse
1874
+
1875
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1876
+
1877
+ request = taple_service_pb2.CreateTableViewRequest(
1878
+ sheet_id=sheet_id,
1879
+ view_name=name,
1880
+ view_type=view_type,
1881
+ visible_columns=visible_columns or [],
1882
+ is_hidden=is_hidden,
1883
+ is_default=is_default,
1884
+ )
1885
+
1886
+ # 处理可选的 JSON 字段
1887
+ if filter_criteria:
1888
+ request.filter_criteria.CopyFrom(self._convert_dict_to_struct(filter_criteria))
1889
+ if sort_criteria:
1890
+ request.sort_criteria.CopyFrom(self._convert_dict_to_struct(sort_criteria))
1891
+ if group_criteria:
1892
+ request.group_criteria.CopyFrom(self._convert_dict_to_struct(group_criteria))
1893
+ if config:
1894
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
1895
+
1896
+ response = stub.CreateTableView(
1897
+ request,
1898
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1899
+ )
1900
+
1901
+ return TableViewResponse(view=self._convert_table_view(response.view))
1902
+
1903
+ @retry_with_backoff(max_retries=3)
1904
+ def get_table_view(
1905
+ self,
1906
+ view_id: str,
1907
+ request_id: Optional[str] = None,
1908
+ **metadata
1909
+ ) -> 'TableViewResponse':
1910
+ """
1911
+ 获取表格视图
1912
+
1913
+ Args:
1914
+ view_id: 视图ID
1915
+ request_id: 请求ID(可选,如果不提供则自动生成)
1916
+ **metadata: 额外的元数据
1917
+
1918
+ Returns:
1919
+ TableViewResponse
1920
+ """
1921
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1922
+ from ...schemas.taple import TableViewResponse
1923
+
1924
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1925
+
1926
+ request = taple_service_pb2.GetTableViewRequest(view_id=view_id)
1927
+
1928
+ response = stub.GetTableView(
1929
+ request,
1930
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1931
+ )
1932
+
1933
+ return TableViewResponse(view=self._convert_table_view(response.view))
1934
+
1935
+ @retry_with_backoff(max_retries=3)
1936
+ def list_table_views(
1937
+ self,
1938
+ *,
1939
+ table_id: Optional[str] = None,
1940
+ sheet_id: Optional[str] = None,
1941
+ view_type: Optional[str] = None,
1942
+ request_id: Optional[str] = None,
1943
+ **metadata
1944
+ ) -> 'ListTableViewsResponse':
1945
+ """
1946
+ 列出表格视图
1947
+
1948
+ Args:
1949
+ table_id: 按表格ID查询(可选)
1950
+ sheet_id: 按工作表ID查询(可选)
1951
+ view_type: 筛选视图类型(可选)
1952
+ request_id: 请求ID(可选,如果不提供则自动生成)
1953
+ **metadata: 额外的元数据
1954
+
1955
+ Returns:
1956
+ ListTableViewsResponse
1957
+ """
1958
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1959
+ from ...schemas.taple import ListTableViewsResponse
1960
+
1961
+ if not table_id and not sheet_id:
1962
+ raise ValidationError("必须提供 table_id 或 sheet_id 之一")
1963
+
1964
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1965
+
1966
+ request = taple_service_pb2.ListTableViewsRequest()
1967
+
1968
+ if table_id:
1969
+ request.table_id = table_id
1970
+ elif sheet_id:
1971
+ request.sheet_id = sheet_id
1972
+
1973
+ if view_type:
1974
+ request.view_type = view_type
1975
+
1976
+ response = stub.ListTableViews(
1977
+ request,
1978
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1979
+ )
1980
+
1981
+ return ListTableViewsResponse(
1982
+ views=[self._convert_table_view(view) for view in response.views],
1983
+ total_count=response.total_count
1984
+ )
1985
+
1986
+ @retry_with_backoff(max_retries=3)
1987
+ def update_table_view(
1988
+ self,
1989
+ view_id: str,
1990
+ *,
1991
+ name: Optional[str] = None,
1992
+ filter_criteria: Optional[Dict[str, Any]] = None,
1993
+ sort_criteria: Optional[Dict[str, Any]] = None,
1994
+ visible_columns: Optional[List[str]] = None,
1995
+ group_criteria: Optional[Dict[str, Any]] = None,
1996
+ is_hidden: Optional[bool] = None,
1997
+ is_default: Optional[bool] = None,
1998
+ config: Optional[Dict[str, Any]] = None,
1999
+ idempotency_key: Optional[str] = None,
2000
+ request_id: Optional[str] = None,
2001
+ **metadata
2002
+ ) -> 'TableViewResponse':
2003
+ """
2004
+ 更新表格视图
2005
+
2006
+ Args:
2007
+ view_id: 视图ID
2008
+ name: 新名称(可选)
2009
+ filter_criteria: 过滤条件(可选)
2010
+ sort_criteria: 排序条件(可选)
2011
+ visible_columns: 可见列列表(可选,传入空列表清空可见列设置)
2012
+ group_criteria: 分组条件(可选)
2013
+ is_hidden: 是否隐藏(可选)
2014
+ is_default: 是否默认视图(可选)
2015
+ config: 扩展配置(可选)
2016
+ idempotency_key: 幂等性键(可选)
2017
+ request_id: 请求ID(可选,如果不提供则自动生成)
2018
+ **metadata: 额外的元数据
2019
+
2020
+ Returns:
2021
+ TableViewResponse
2022
+ """
2023
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2024
+ from ...schemas.taple import TableViewResponse
2025
+ from google.protobuf.struct_pb2 import ListValue
2026
+
2027
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2028
+
2029
+ request = taple_service_pb2.UpdateTableViewRequest(view_id=view_id)
2030
+
2031
+ if name is not None:
2032
+ request.view_name = name
2033
+
2034
+ # 处理可选的 JSON 字段
2035
+ if filter_criteria is not None:
2036
+ request.filter_criteria.CopyFrom(self._convert_dict_to_struct(filter_criteria))
2037
+ if sort_criteria is not None:
2038
+ request.sort_criteria.CopyFrom(self._convert_dict_to_struct(sort_criteria))
2039
+ if visible_columns is not None:
2040
+ from google.protobuf.struct_pb2 import Value
2041
+ list_value = ListValue()
2042
+ for col in visible_columns:
2043
+ value = Value()
2044
+ value.string_value = str(col)
2045
+ list_value.values.append(value)
2046
+ request.visible_columns.CopyFrom(list_value)
2047
+ if group_criteria is not None:
2048
+ request.group_criteria.CopyFrom(self._convert_dict_to_struct(group_criteria))
2049
+
2050
+ # 处理布尔字段
2051
+ if is_hidden is not None:
2052
+ request.is_hidden = is_hidden
2053
+ if is_default is not None:
2054
+ request.is_default = is_default
2055
+
2056
+ if config is not None:
2057
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
2058
+
2059
+ response = stub.UpdateTableView(
2060
+ request,
2061
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2062
+ )
2063
+
2064
+ return TableViewResponse(view=self._convert_table_view(response.view))
2065
+
2066
+ @retry_with_backoff(max_retries=3)
2067
+ def delete_table_view(
2068
+ self,
2069
+ view_id: str,
2070
+ *,
2071
+ idempotency_key: Optional[str] = None,
2072
+ request_id: Optional[str] = None,
2073
+ **metadata
2074
+ ) -> None:
2075
+ """
2076
+ 删除表格视图
2077
+
2078
+ Args:
2079
+ view_id: 视图ID
2080
+ idempotency_key: 幂等性键(可选)
2081
+ request_id: 请求ID(可选,如果不提供则自动生成)
2082
+ **metadata: 额外的元数据
2083
+ """
2084
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2085
+
2086
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2087
+
2088
+ request = taple_service_pb2.DeleteTableViewRequest(view_id=view_id)
2089
+
2090
+ stub.DeleteTableView(
2091
+ request,
2092
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2093
+ )
2094
+
2095
+ @retry_with_backoff(max_retries=3)
2096
+ def update_table_view_config(
2097
+ self,
2098
+ view_id: str,
2099
+ config: Dict[str, Any],
2100
+ *,
2101
+ idempotency_key: Optional[str] = None,
2102
+ request_id: Optional[str] = None,
2103
+ **metadata
2104
+ ) -> 'TableViewResponse':
2105
+ """
2106
+ 更新视图配置
2107
+
2108
+ Args:
2109
+ view_id: 视图ID
2110
+ config: 新配置
2111
+ idempotency_key: 幂等性键(可选)
2112
+ request_id: 请求ID(可选,如果不提供则自动生成)
2113
+ **metadata: 额外的元数据
2114
+
2115
+ Returns:
2116
+ TableViewResponse
2117
+ """
2118
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2119
+ from ...schemas.taple import TableViewResponse
2120
+
2121
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2122
+
2123
+ request = taple_service_pb2.UpdateTableViewConfigRequest(view_id=view_id)
2124
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
2125
+
2126
+ response = stub.UpdateTableViewConfig(
2127
+ request,
2128
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2129
+ )
2130
+
2131
+ return TableViewResponse(view=self._convert_table_view(response.view))
2132
+
2133
+ def _convert_table_view(self, proto_view) -> 'TableView':
2134
+ """转换 proto TableView 到 Python TableView 模型"""
2135
+ from ...schemas.taple import TableView
2136
+ from google.protobuf.json_format import MessageToDict
2137
+
2138
+ # 处理 visible_columns 的转换
2139
+ visible_columns = None
2140
+ if proto_view.visible_columns:
2141
+ visible_columns = [str(col) for col in proto_view.visible_columns.values]
2142
+
2143
+ return TableView(
2144
+ id=proto_view.id,
2145
+ table_id=proto_view.table_id,
2146
+ sheet_id=proto_view.sheet_id,
2147
+ org_id=proto_view.org_id,
2148
+ user_id=proto_view.user_id,
2149
+ file_id=proto_view.file_id,
2150
+
2151
+ # 视图配置字段
2152
+ filter_criteria=MessageToDict(proto_view.filter_criteria) if proto_view.filter_criteria else None,
2153
+ sort_criteria=MessageToDict(proto_view.sort_criteria) if proto_view.sort_criteria else None,
2154
+ visible_columns=visible_columns,
2155
+ group_criteria=MessageToDict(proto_view.group_criteria) if proto_view.group_criteria else None,
2156
+
2157
+ # 创建者信息
2158
+ created_by_role=proto_view.created_by_role,
2159
+ created_by=proto_view.created_by,
2160
+
2161
+ # 视图基本信息
2162
+ view_name=proto_view.view_name,
2163
+ view_type=proto_view.view_type,
2164
+
2165
+ # 视图状态
2166
+ is_hidden=proto_view.is_hidden,
2167
+ is_default=proto_view.is_default,
2168
+
2169
+ # 扩展配置
2170
+ config=MessageToDict(proto_view.config) if proto_view.config else None,
2171
+
2172
+ # 时间戳
2173
+ created_at=timestamp_to_datetime(proto_view.created_at),
2174
+ updated_at=timestamp_to_datetime(proto_view.updated_at),
2175
+ deleted_at=timestamp_to_datetime(proto_view.deleted_at) if proto_view.deleted_at else None
2176
+ )
2177
+
2178
+ @retry_with_backoff(max_retries=3)
2179
+ def batch_create_table_views(
2180
+ self,
2181
+ sheet_id: str,
2182
+ views: List[Dict[str, Any]],
2183
+ *,
2184
+ request_id: Optional[str] = None,
2185
+ **metadata
2186
+ ) -> 'BatchCreateTableViewsResponse':
2187
+ """
2188
+ 批量创建表格视图
2189
+
2190
+ Args:
2191
+ sheet_id: 所属工作表ID
2192
+ views: 要创建的视图列表,每个视图包含以下字段:
2193
+ - view_name: 视图名称
2194
+ - view_type: 视图类型
2195
+ - filter_criteria: 过滤条件(可选)
2196
+ - sort_criteria: 排序条件(可选)
2197
+ - visible_columns: 可见列列表(可选)
2198
+ - group_criteria: 分组条件(可选)
2199
+ - is_hidden: 是否隐藏(可选,默认False)
2200
+ - is_default: 是否默认视图(可选,默认False)
2201
+ - config: 扩展配置(可选)
2202
+ request_id: 请求ID(可选,如果不提供则自动生成)
2203
+ **metadata: 额外的元数据
2204
+
2205
+ Returns:
2206
+ BatchCreateTableViewsResponse
2207
+ """
2208
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2209
+ from ...schemas.taple import BatchCreateTableViewsResponse, BatchCreateTableViewResult
2210
+
2211
+ stub = self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2212
+
2213
+ # 构建请求
2214
+ request = taple_service_pb2.BatchCreateTableViewsRequest(sheet_id=sheet_id)
2215
+
2216
+ for view_data in views:
2217
+ view_req = taple_service_pb2.CreateTableViewData(
2218
+ view_name=view_data['view_name'],
2219
+ view_type=view_data['view_type'],
2220
+ visible_columns=view_data.get('visible_columns', []),
2221
+ is_hidden=view_data.get('is_hidden', False),
2222
+ is_default=view_data.get('is_default', False),
2223
+ )
2224
+
2225
+ # 处理可选的 JSON 字段
2226
+ if 'filter_criteria' in view_data:
2227
+ view_req.filter_criteria.CopyFrom(self._convert_dict_to_struct(view_data['filter_criteria']))
2228
+ if 'sort_criteria' in view_data:
2229
+ view_req.sort_criteria.CopyFrom(self._convert_dict_to_struct(view_data['sort_criteria']))
2230
+ if 'group_criteria' in view_data:
2231
+ view_req.group_criteria.CopyFrom(self._convert_dict_to_struct(view_data['group_criteria']))
2232
+ if 'config' in view_data:
2233
+ view_req.config.CopyFrom(self._convert_dict_to_struct(view_data['config']))
2234
+
2235
+ request.views.append(view_req)
2236
+
2237
+ response = stub.BatchCreateTableViews(
2238
+ request,
2239
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2240
+ )
2241
+
2242
+ # 转换响应
2243
+ results = []
2244
+ for result in response.results:
2245
+ results.append(BatchCreateTableViewResult(
2246
+ success=result.success,
2247
+ view=self._convert_table_view(result.view) if result.view else None,
2248
+ error_message=result.error_message if result.error_message else None,
2249
+ view_name=result.view_name if result.view_name else None
2250
+ ))
2251
+
2252
+ return BatchCreateTableViewsResponse(
2253
+ results=results,
2254
+ success_count=response.success_count,
2255
+ failed_count=response.failed_count
2256
+ )