tamar-file-hub-client 0.0.9__py3-none-any.whl → 0.0.11__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,2291 +1,2285 @@
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
- )
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[Dict[str, bool]] = 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: 可见列配置(可选),字典格式 {column_id: bool},值为True表示显示该列
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
+ is_hidden=is_hidden,
1917
+ is_default=is_default,
1918
+ )
1919
+
1920
+ # 处理可选的 JSON 字段
1921
+ if visible_columns:
1922
+ request.visible_columns.CopyFrom(self._convert_dict_to_struct(visible_columns))
1923
+ if filter_criteria:
1924
+ request.filter_criteria.CopyFrom(self._convert_dict_to_struct(filter_criteria))
1925
+ if sort_criteria:
1926
+ request.sort_criteria.CopyFrom(self._convert_dict_to_struct(sort_criteria))
1927
+ if group_criteria:
1928
+ request.group_criteria.CopyFrom(self._convert_dict_to_struct(group_criteria))
1929
+ if config:
1930
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
1931
+
1932
+ response = await stub.CreateTableView(
1933
+ request,
1934
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1935
+ )
1936
+
1937
+ return TableViewResponse(view=self._convert_table_view(response.view))
1938
+
1939
+ @retry_with_backoff(max_retries=3)
1940
+ async def get_table_view(
1941
+ self,
1942
+ view_id: str,
1943
+ request_id: Optional[str] = None,
1944
+ **metadata
1945
+ ) -> 'TableViewResponse':
1946
+ """
1947
+ 获取表格视图
1948
+
1949
+ Args:
1950
+ view_id: 视图ID
1951
+ request_id: 请求ID(可选,如果不提供则自动生成)
1952
+ **metadata: 额外的元数据
1953
+
1954
+ Returns:
1955
+ TableViewResponse
1956
+ """
1957
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1958
+ from ...schemas.taple import TableViewResponse
1959
+
1960
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
1961
+
1962
+ request = taple_service_pb2.GetTableViewRequest(view_id=view_id)
1963
+
1964
+ response = await stub.GetTableView(
1965
+ request,
1966
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
1967
+ )
1968
+
1969
+ return TableViewResponse(view=self._convert_table_view(response.view))
1970
+
1971
+ @retry_with_backoff(max_retries=3)
1972
+ async def list_table_views(
1973
+ self,
1974
+ *,
1975
+ table_id: Optional[str] = None,
1976
+ sheet_id: Optional[str] = None,
1977
+ view_type: Optional[str] = None,
1978
+ request_id: Optional[str] = None,
1979
+ **metadata
1980
+ ) -> 'ListTableViewsResponse':
1981
+ """
1982
+ 列出表格视图
1983
+
1984
+ Args:
1985
+ table_id: 按表格ID查询(可选)
1986
+ sheet_id: 按工作表ID查询(可选)
1987
+ view_type: 筛选视图类型(可选)
1988
+ request_id: 请求ID(可选,如果不提供则自动生成)
1989
+ **metadata: 额外的元数据
1990
+
1991
+ Returns:
1992
+ ListTableViewsResponse
1993
+ """
1994
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
1995
+ from ...schemas.taple import ListTableViewsResponse
1996
+
1997
+ if not table_id and not sheet_id:
1998
+ raise ValidationError("必须提供 table_id 或 sheet_id 之一")
1999
+
2000
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2001
+
2002
+ request = taple_service_pb2.ListTableViewsRequest()
2003
+
2004
+ if table_id:
2005
+ request.table_id = table_id
2006
+ elif sheet_id:
2007
+ request.sheet_id = sheet_id
2008
+
2009
+ if view_type:
2010
+ request.view_type = view_type
2011
+
2012
+ response = await stub.ListTableViews(
2013
+ request,
2014
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2015
+ )
2016
+
2017
+ return ListTableViewsResponse(
2018
+ views=[self._convert_table_view(view) for view in response.views],
2019
+ total_count=response.total_count
2020
+ )
2021
+
2022
+ @retry_with_backoff(max_retries=3)
2023
+ async def update_table_view(
2024
+ self,
2025
+ view_id: str,
2026
+ *,
2027
+ name: Optional[str] = None,
2028
+ filter_criteria: Optional[Dict[str, Any]] = None,
2029
+ sort_criteria: Optional[Dict[str, Any]] = None,
2030
+ visible_columns: Optional[Dict[str, bool]] = None,
2031
+ group_criteria: Optional[Dict[str, Any]] = None,
2032
+ is_hidden: Optional[bool] = None,
2033
+ is_default: Optional[bool] = None,
2034
+ config: Optional[Dict[str, Any]] = None,
2035
+ idempotency_key: Optional[str] = None,
2036
+ request_id: Optional[str] = None,
2037
+ **metadata
2038
+ ) -> 'TableViewResponse':
2039
+ """
2040
+ 更新表格视图
2041
+
2042
+ Args:
2043
+ view_id: 视图ID
2044
+ name: 新名称(可选)
2045
+ filter_criteria: 过滤条件(可选)
2046
+ sort_criteria: 排序条件(可选)
2047
+ visible_columns: 可见列配置(可选),字典格式 {column_id: bool},传入空字典清空可见列设置
2048
+ group_criteria: 分组条件(可选)
2049
+ is_hidden: 是否隐藏(可选)
2050
+ is_default: 是否默认视图(可选)
2051
+ config: 扩展配置(可选)
2052
+ idempotency_key: 幂等性键(可选)
2053
+ request_id: 请求ID(可选,如果不提供则自动生成)
2054
+ **metadata: 额外的元数据
2055
+
2056
+ Returns:
2057
+ TableViewResponse
2058
+ """
2059
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2060
+ from ...schemas.taple import TableViewResponse
2061
+ from google.protobuf.struct_pb2 import ListValue
2062
+
2063
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2064
+
2065
+ request = taple_service_pb2.UpdateTableViewRequest(view_id=view_id)
2066
+
2067
+ if name is not None:
2068
+ request.view_name = name
2069
+
2070
+ # 处理可选的 JSON 字段
2071
+ if filter_criteria is not None:
2072
+ request.filter_criteria.CopyFrom(self._convert_dict_to_struct(filter_criteria))
2073
+ if sort_criteria is not None:
2074
+ request.sort_criteria.CopyFrom(self._convert_dict_to_struct(sort_criteria))
2075
+ if visible_columns is not None:
2076
+ request.visible_columns.CopyFrom(self._convert_dict_to_struct(visible_columns))
2077
+ if group_criteria is not None:
2078
+ request.group_criteria.CopyFrom(self._convert_dict_to_struct(group_criteria))
2079
+
2080
+ # 处理布尔字段
2081
+ if is_hidden is not None:
2082
+ request.is_hidden = is_hidden
2083
+ if is_default is not None:
2084
+ request.is_default = is_default
2085
+
2086
+ if config is not None:
2087
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
2088
+
2089
+ response = await stub.UpdateTableView(
2090
+ request,
2091
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2092
+ )
2093
+
2094
+ return TableViewResponse(view=self._convert_table_view(response.view))
2095
+
2096
+ @retry_with_backoff(max_retries=3)
2097
+ async def delete_table_view(
2098
+ self,
2099
+ view_id: str,
2100
+ *,
2101
+ idempotency_key: Optional[str] = None,
2102
+ request_id: Optional[str] = None,
2103
+ **metadata
2104
+ ) -> None:
2105
+ """
2106
+ 删除表格视图
2107
+
2108
+ Args:
2109
+ view_id: 视图ID
2110
+ idempotency_key: 幂等性键(可选)
2111
+ request_id: 请求ID(可选,如果不提供则自动生成)
2112
+ **metadata: 额外的元数据
2113
+ """
2114
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2115
+
2116
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2117
+
2118
+ request = taple_service_pb2.DeleteTableViewRequest(view_id=view_id)
2119
+
2120
+ await stub.DeleteTableView(
2121
+ request,
2122
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2123
+ )
2124
+
2125
+ @retry_with_backoff(max_retries=3)
2126
+ async def update_table_view_config(
2127
+ self,
2128
+ view_id: str,
2129
+ config: Dict[str, Any],
2130
+ *,
2131
+ idempotency_key: Optional[str] = None,
2132
+ request_id: Optional[str] = None,
2133
+ **metadata
2134
+ ) -> 'TableViewResponse':
2135
+ """
2136
+ 更新视图配置
2137
+
2138
+ Args:
2139
+ view_id: 视图ID
2140
+ config: 新配置
2141
+ idempotency_key: 幂等性键(可选)
2142
+ request_id: 请求ID(可选,如果不提供则自动生成)
2143
+ **metadata: 额外的元数据
2144
+
2145
+ Returns:
2146
+ TableViewResponse
2147
+ """
2148
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2149
+ from ...schemas.taple import TableViewResponse
2150
+
2151
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2152
+
2153
+ request = taple_service_pb2.UpdateTableViewConfigRequest(view_id=view_id)
2154
+ request.config.CopyFrom(self._convert_dict_to_struct(config))
2155
+
2156
+ response = await stub.UpdateTableViewConfig(
2157
+ request,
2158
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2159
+ )
2160
+
2161
+ return TableViewResponse(view=self._convert_table_view(response.view))
2162
+
2163
+ def _convert_table_view(self, proto_view) -> 'TableView':
2164
+ """转换 proto TableView 到 Python TableView 模型"""
2165
+ from ...schemas.taple import TableView
2166
+ from google.protobuf.json_format import MessageToDict
2167
+
2168
+ # 处理 visible_columns 的转换
2169
+ visible_columns = MessageToDict(proto_view.visible_columns) if proto_view.HasField('visible_columns') else None
2170
+
2171
+ return TableView(
2172
+ id=proto_view.id,
2173
+ table_id=proto_view.table_id,
2174
+ sheet_id=proto_view.sheet_id,
2175
+ org_id=proto_view.org_id,
2176
+ user_id=proto_view.user_id,
2177
+ file_id=proto_view.file_id,
2178
+
2179
+ # 视图配置字段
2180
+ filter_criteria=MessageToDict(proto_view.filter_criteria) if proto_view.filter_criteria else None,
2181
+ sort_criteria=MessageToDict(proto_view.sort_criteria) if proto_view.sort_criteria else None,
2182
+ visible_columns=visible_columns,
2183
+ group_criteria=MessageToDict(proto_view.group_criteria) if proto_view.group_criteria else None,
2184
+
2185
+ # 创建者信息
2186
+ created_by_role=proto_view.created_by_role,
2187
+ created_by=proto_view.created_by,
2188
+
2189
+ # 视图基本信息
2190
+ view_name=proto_view.view_name,
2191
+ view_type=proto_view.view_type,
2192
+
2193
+ # 视图状态
2194
+ is_hidden=proto_view.is_hidden,
2195
+ is_default=proto_view.is_default,
2196
+
2197
+ # 扩展配置
2198
+ config=MessageToDict(proto_view.config) if proto_view.config else None,
2199
+
2200
+ # 时间戳
2201
+ created_at=timestamp_to_datetime(proto_view.created_at),
2202
+ updated_at=timestamp_to_datetime(proto_view.updated_at),
2203
+ deleted_at=timestamp_to_datetime(proto_view.deleted_at) if proto_view.deleted_at else None
2204
+ )
2205
+
2206
+ @retry_with_backoff(max_retries=3)
2207
+ async def batch_create_table_views(
2208
+ self,
2209
+ sheet_id: str,
2210
+ views: List[Dict[str, Any]],
2211
+ *,
2212
+ request_id: Optional[str] = None,
2213
+ **metadata
2214
+ ) -> 'BatchCreateTableViewsResponse':
2215
+ """
2216
+ 批量创建表格视图
2217
+
2218
+ Args:
2219
+ sheet_id: 所属工作表ID
2220
+ views: 要创建的视图列表,每个视图包含以下字段:
2221
+ - view_name: 视图名称
2222
+ - view_type: 视图类型
2223
+ - filter_criteria: 过滤条件(可选)
2224
+ - sort_criteria: 排序条件(可选)
2225
+ - visible_columns: 可见列配置(可选),字典格式 {column_id: bool}
2226
+ - group_criteria: 分组条件(可选)
2227
+ - is_hidden: 是否隐藏(可选,默认False)
2228
+ - is_default: 是否默认视图(可选,默认False)
2229
+ - config: 扩展配置(可选)
2230
+ request_id: 请求ID(可选,如果不提供则自动生成)
2231
+ **metadata: 额外的元数据
2232
+
2233
+ Returns:
2234
+ BatchCreateTableViewsResponse
2235
+ """
2236
+ from ...rpc.gen import taple_service_pb2, taple_service_pb2_grpc
2237
+ from ...schemas.taple import BatchCreateTableViewsResponse, BatchCreateTableViewResult
2238
+
2239
+ stub = await self.client.get_stub(taple_service_pb2_grpc.TapleServiceStub)
2240
+
2241
+ # 构建请求
2242
+ request = taple_service_pb2.BatchCreateTableViewsRequest(sheet_id=sheet_id)
2243
+
2244
+ for view_data in views:
2245
+ view_req = taple_service_pb2.CreateTableViewData(
2246
+ view_name=view_data['view_name'],
2247
+ view_type=view_data['view_type'],
2248
+ is_hidden=view_data.get('is_hidden', False),
2249
+ is_default=view_data.get('is_default', False),
2250
+ )
2251
+
2252
+ # 处理可选的 JSON 字段
2253
+ if 'visible_columns' in view_data:
2254
+ view_req.visible_columns.CopyFrom(self._convert_dict_to_struct(view_data['visible_columns']))
2255
+ if 'filter_criteria' in view_data:
2256
+ view_req.filter_criteria.CopyFrom(self._convert_dict_to_struct(view_data['filter_criteria']))
2257
+ if 'sort_criteria' in view_data:
2258
+ view_req.sort_criteria.CopyFrom(self._convert_dict_to_struct(view_data['sort_criteria']))
2259
+ if 'group_criteria' in view_data:
2260
+ view_req.group_criteria.CopyFrom(self._convert_dict_to_struct(view_data['group_criteria']))
2261
+ if 'config' in view_data:
2262
+ view_req.config.CopyFrom(self._convert_dict_to_struct(view_data['config']))
2263
+
2264
+ request.views.append(view_req)
2265
+
2266
+ response = await stub.BatchCreateTableViews(
2267
+ request,
2268
+ metadata=self.client.build_metadata(request_id=request_id, **metadata)
2269
+ )
2270
+
2271
+ # 转换响应
2272
+ results = []
2273
+ for result in response.results:
2274
+ results.append(BatchCreateTableViewResult(
2275
+ success=result.success,
2276
+ view=self._convert_table_view(result.view) if result.view else None,
2277
+ error_message=result.error_message if result.error_message else None,
2278
+ view_name=result.view_name if result.view_name else None
2279
+ ))
2280
+
2281
+ return BatchCreateTableViewsResponse(
2282
+ results=results,
2283
+ success_count=response.success_count,
2284
+ failed_count=response.failed_count
2285
+ )