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