tamar-file-hub-client 0.0.1__tar.gz → 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. tamar_file_hub_client-0.0.2/PKG-INFO +2050 -0
  2. tamar_file_hub_client-0.0.2/README.md +2004 -0
  3. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/__init__.py +39 -0
  4. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/client.py +43 -6
  5. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/async_client.py +91 -11
  6. tamar_file_hub_client-0.0.2/file_hub_client/rpc/gen/taple_service_pb2.py +225 -0
  7. tamar_file_hub_client-0.0.2/file_hub_client/rpc/gen/taple_service_pb2_grpc.py +1626 -0
  8. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/generate_grpc.py +2 -2
  9. tamar_file_hub_client-0.0.2/file_hub_client/rpc/interceptors.py +550 -0
  10. tamar_file_hub_client-0.0.2/file_hub_client/rpc/protos/taple_service.proto +874 -0
  11. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/sync_client.py +91 -9
  12. tamar_file_hub_client-0.0.2/file_hub_client/schemas/__init__.py +103 -0
  13. tamar_file_hub_client-0.0.2/file_hub_client/schemas/taple.py +413 -0
  14. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/__init__.py +5 -0
  15. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/async_blob_service.py +558 -482
  16. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/async_file_service.py +18 -9
  17. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/base_file_service.py +19 -6
  18. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/sync_blob_service.py +554 -478
  19. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/sync_file_service.py +18 -9
  20. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/folder/async_folder_service.py +20 -11
  21. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/folder/sync_folder_service.py +20 -11
  22. tamar_file_hub_client-0.0.2/file_hub_client/services/taple/__init__.py +10 -0
  23. tamar_file_hub_client-0.0.2/file_hub_client/services/taple/async_taple_service.py +2281 -0
  24. tamar_file_hub_client-0.0.2/file_hub_client/services/taple/base_taple_service.py +353 -0
  25. tamar_file_hub_client-0.0.2/file_hub_client/services/taple/idempotent_taple_mixin.py +142 -0
  26. tamar_file_hub_client-0.0.2/file_hub_client/services/taple/sync_taple_service.py +2256 -0
  27. tamar_file_hub_client-0.0.2/file_hub_client/utils/__init__.py +90 -0
  28. tamar_file_hub_client-0.0.2/file_hub_client/utils/file_utils.py +153 -0
  29. tamar_file_hub_client-0.0.2/file_hub_client/utils/idempotency.py +196 -0
  30. tamar_file_hub_client-0.0.2/file_hub_client/utils/logging.py +315 -0
  31. tamar_file_hub_client-0.0.2/file_hub_client/utils/retry.py +308 -0
  32. tamar_file_hub_client-0.0.2/file_hub_client/utils/smart_retry.py +403 -0
  33. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/setup.py +4 -2
  34. tamar_file_hub_client-0.0.2/tamar_file_hub_client.egg-info/PKG-INFO +2050 -0
  35. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/SOURCES.txt +13 -0
  36. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/requires.txt +6 -0
  37. tamar_file_hub_client-0.0.1/PKG-INFO +0 -874
  38. tamar_file_hub_client-0.0.1/README.md +0 -830
  39. tamar_file_hub_client-0.0.1/file_hub_client/schemas/__init__.py +0 -43
  40. tamar_file_hub_client-0.0.1/file_hub_client/utils/__init__.py +0 -48
  41. tamar_file_hub_client-0.0.1/file_hub_client/utils/file_utils.py +0 -105
  42. tamar_file_hub_client-0.0.1/file_hub_client/utils/retry.py +0 -69
  43. tamar_file_hub_client-0.0.1/tamar_file_hub_client.egg-info/PKG-INFO +0 -874
  44. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/MANIFEST.in +0 -0
  45. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/__init__.py +0 -0
  46. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/export_format.py +0 -0
  47. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/role.py +0 -0
  48. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/upload_mode.py +0 -0
  49. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/errors/__init__.py +0 -0
  50. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/errors/exceptions.py +0 -0
  51. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/py.typed +0 -0
  52. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/__init__.py +0 -0
  53. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/__init__.py +0 -0
  54. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/file_service_pb2.py +0 -0
  55. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/file_service_pb2_grpc.py +0 -0
  56. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/folder_service_pb2.py +0 -0
  57. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/folder_service_pb2_grpc.py +0 -0
  58. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/protos/file_service.proto +0 -0
  59. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/protos/folder_service.proto +0 -0
  60. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/context.py +0 -0
  61. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/file.py +0 -0
  62. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/folder.py +0 -0
  63. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/__init__.py +0 -0
  64. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/folder/__init__.py +0 -0
  65. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/converter.py +0 -0
  66. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/download_helper.py +0 -0
  67. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/upload_helper.py +0 -0
  68. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/setup.cfg +0 -0
  69. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/dependency_links.txt +0 -0
  70. {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/top_level.txt +0 -0
@@ -0,0 +1,2050 @@
1
+ Metadata-Version: 2.4
2
+ Name: tamar-file-hub-client
3
+ Version: 0.0.2
4
+ Summary: A Python SDK for gRPC-based file management system
5
+ Home-page: https://github.com/Tamar-Edge-AI/file-hub-client
6
+ Author: Oscar Ou
7
+ Author-email: oscar.ou@tamaredge.ai
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Classifier: Topic :: Communications :: File Sharing
22
+ Classifier: Framework :: AsyncIO
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ Requires-Dist: grpcio>=1.67.1
27
+ Requires-Dist: grpcio-tools>=1.67.1
28
+ Requires-Dist: protobuf>=4.21.0
29
+ Requires-Dist: pydantic>=2.0.0
30
+ Requires-Dist: typing-extensions>=4.0.0; python_version < "3.10"
31
+ Requires-Dist: requests>=2.28.0
32
+ Requires-Dist: aiohttp>=3.8.0
33
+ Requires-Dist: aiofiles>=23.0.0
34
+ Requires-Dist: python-magic; platform_system != "Windows"
35
+ Requires-Dist: python-magic-bin~=0.4.14; platform_system == "Windows"
36
+ Dynamic: author
37
+ Dynamic: author-email
38
+ Dynamic: classifier
39
+ Dynamic: description
40
+ Dynamic: description-content-type
41
+ Dynamic: home-page
42
+ Dynamic: license
43
+ Dynamic: requires-dist
44
+ Dynamic: requires-python
45
+ Dynamic: summary
46
+
47
+ # File Hub Client
48
+
49
+ 一个基于 gRPC 的文件管理系统 Python SDK,提供异步和同步两种客户端实现。
50
+
51
+ ## 功能特性
52
+
53
+ - 🚀 **双模式支持**:提供异步(AsyncIO)和同步两种客户端实现
54
+ - 📁 **完整的文件管理**:支持文件上传、下载、重命名、删除等操作
55
+ - 📂 **文件夹管理**:支持文件夹的创建、重命名、移动、删除
56
+ - 🔗 **文件分享**:支持生成分享链接,设置访问权限和密码
57
+ - 🔄 **多种上传方式**:支持直传、断点续传、客户端直传到对象存储
58
+ - 🛡️ **错误处理**:完善的异常体系和错误重试机制
59
+ - 🔒 **TLS/SSL 支持**:支持安全的加密连接,保护数据传输
60
+ - 🔁 **自动重试**:连接失败时自动重试,提高可靠性
61
+ - 📝 **类型注解**:完整的类型提示支持
62
+ - 🧩 **模块化设计**:清晰的代码结构,易于扩展
63
+ - 🏗️ **分层服务架构**:文件服务分为传统文件(blob)和自定义类型(结构化数据),每种类型独立服务,语义清晰
64
+ - 🔧 **环境变量配置**:支持通过环境变量配置所有参数
65
+ - 👤 **用户上下文管理**:支持区分资源所有权(ownership)和操作者(operator)
66
+ - 📊 **请求上下文追踪**:自动收集客户端信息,支持请求追踪和审计
67
+ - 📊 **Taple 电子表格**:完整的类 Excel 功能支持,包括数据导入导出、查询筛选、样式管理等
68
+ - 📡 **gRPC 请求日志**:自动记录所有 gRPC 请求和响应,支持 JSON 格式日志
69
+
70
+ ## 项目结构
71
+
72
+ ```
73
+ file-hub-client/
74
+ ├── file_hub_client/ # 主包目录
75
+ │ ├── __init__.py # 包初始化,导出版本信息和主要类
76
+ │ ├── client.py # 客户端入口(AsyncTamarFileHubClient, TamarFileHubClient)
77
+ │ ├── py.typed # PEP 561 类型标记文件
78
+ │ │
79
+ │ ├── rpc/ # gRPC 相关
80
+ │ │ ├── __init__.py # RPC 模块初始化
81
+ │ │ ├── async_client.py # 异步 gRPC 客户端基类
82
+ │ │ ├── sync_client.py # 同步 gRPC 客户端基类
83
+ │ │ ├── interceptors.py # gRPC 拦截器(自动日志记录)
84
+ │ │ ├── generate_grpc.py # Proto 文件代码生成脚本
85
+ │ │ ├── protos/ # Protocol Buffer 定义
86
+ │ │ │ ├── file_service.proto # 文件服务定义
87
+ │ │ │ ├── folder_service.proto # 文件夹服务定义
88
+ │ │ │ └── taple_service.proto # Taple 服务定义
89
+ │ │ └── gen/ # 生成的 gRPC 代码(自动生成)
90
+ │ │ ├── __init__.py
91
+ │ │ ├── file_service_pb2.py
92
+ │ │ ├── file_service_pb2_grpc.py
93
+ │ │ ├── folder_service_pb2.py
94
+ │ │ └── folder_service_pb2_grpc.py
95
+ │ │
96
+ │ ├── services/ # 服务层(分层架构:传统文件用blob_service,自定义类型独立service)
97
+ │ │ ├── __init__.py # 服务模块导出
98
+ │ │ ├── file/ # 文件服务(统一入口,按类型分层)
99
+ │ │ │ ├── __init__.py
100
+ │ │ │ ├── base_file_service.py # 文件服务基类
101
+ │ │ │ ├── async_blob_service.py # 异步二进制大对象服务(传统文件上传下载)
102
+ │ │ │ ├── sync_blob_service.py # 同步二进制大对象服务(传统文件上传下载)
103
+ │ │ │ ├── async_file_service.py # 异步文件元数据服务(所有类型通用)
104
+ │ │ │ └── sync_file_service.py # 同步文件元数据服务(所有类型通用)
105
+ │ │ │ # 未来扩展:spreadsheet_service, document_service, canvas_service等
106
+ │ │ ├── folder/ # 文件夹服务
107
+ │ │ │ ├── __init__.py
108
+ │ │ │ ├── async_folder_service.py # 异步文件夹服务实现
109
+ │ │ │ └── sync_folder_service.py # 同步文件夹服务实现
110
+ │ │ └── taple/ # Taple(电子表格)服务
111
+ │ │ ├── __init__.py
112
+ │ │ ├── base_taple_service.py # Taple 服务基类
113
+ │ │ ├── async_taple_service.py # 异步 Taple 服务实现
114
+ │ │ ├── sync_taple_service.py # 同步 Taple 服务实现
115
+ │ │ └── idempotent_taple_mixin.py # 幂等性支持混入类
116
+ │ │
117
+ │ ├── schemas/ # 数据模型(Pydantic)
118
+ │ │ ├── __init__.py # 模型导出
119
+ │ │ ├── file.py # 文件相关模型
120
+ │ │ ├── folder.py # 文件夹相关模型
121
+ │ │ ├── context.py # 上下文相关模型(用户和请求上下文)
122
+ │ │ └── taple.py # Taple 相关模型
123
+ │ │
124
+ │ ├── enums/ # 枚举定义
125
+ │ │ ├── __init__.py # 枚举导出
126
+ │ │ ├── role.py # 角色枚举(ACCOUNT, AGENT, SYSTEM)
127
+ │ │ ├── upload_mode.py # 上传模式枚举
128
+ │ │ └── export_format.py # 导出格式枚举
129
+ │ │
130
+ │ ├── errors/ # 异常定义
131
+ │ │ ├── __init__.py # 异常导出
132
+ │ │ └── exceptions.py # 自定义异常类
133
+ │ │
134
+ │ └── utils/ # 工具函数
135
+ │ ├── __init__.py # 工具函数导出
136
+ │ ├── file_utils.py # 文件操作工具
137
+ │ ├── converter.py # 数据转换工具
138
+ │ ├── retry.py # 重试装饰器
139
+ │ ├── upload_helper.py # 上传辅助工具(HTTP上传器)
140
+ │ ├── download_helper.py # 下载辅助工具(HTTP下载器)
141
+ │ ├── idempotency.py # 幂等性支持工具
142
+ │ └── logging.py # 日志配置和工具
143
+
144
+ ├── tests/ # 测试文件
145
+ │ └── taple/ # Taple 功能测试
146
+ │ ├── config.py # 测试配置
147
+ │ ├── test_*.py # 各种功能测试脚本
148
+ │ └── run_all_tests.py # 运行所有测试
149
+
150
+ ├── .gitignore # Git 忽略文件配置
151
+ ├── .env.example # 环境变量配置示例
152
+ ├── README.md # 项目说明文档(本文件)
153
+ ├── setup.py # 安装配置文件
154
+ ├── pyproject.toml # 项目配置文件(PEP 518)
155
+ └── MANIFEST.in # 打包配置文件
156
+ ```
157
+
158
+ ## 模块说明
159
+
160
+ ### 核心模块
161
+
162
+ - **client.py**: 提供 `AsyncTamarFileHubClient` 和 `TamarFileHubClient` 两个客户端类,是使用 SDK 的入口点
163
+ - 提供了预配置的单例客户端 `tamar_client` 和 `async_tamar_client`
164
+ - 支持分层服务访问:
165
+ - `blobs`(传统文件内容:上传/下载)
166
+ - `files`(文件元数据:所有类型通用的管理操作)
167
+ - `folders`(文件夹管理)
168
+ - `taples`(电子表格服务)
169
+ - 未来扩展:`documents`、`canvases` 等自定义类型服务
170
+
171
+ ### RPC 模块 (`rpc/`)
172
+
173
+ - **async_client.py/sync_client.py**: gRPC 客户端基类,处理连接管理、元数据构建、stub 缓存
174
+ - **interceptors.py**: gRPC 拦截器,自动记录所有请求和响应日志
175
+ - **generate_grpc.py**: 从 proto 文件生成 Python 代码的脚本
176
+ - **protos/**: 存放 Protocol Buffer 定义文件
177
+ - `file_service.proto`: 定义文件相关的 RPC 服务
178
+ - `folder_service.proto`: 定义文件夹相关的 RPC 服务
179
+ - `taple_service.proto`: 定义 Taple 电子表格相关的 RPC 服务
180
+
181
+ ### 服务模块 (`services/`)
182
+
183
+ #### 分层服务架构设计
184
+
185
+ File Hub Client 采用分层服务架构,将文件服务按类型和语义进行清晰分离:
186
+
187
+ **📁 统一文件入口**:所有文件类型都通过统一的 `files` 接口进行元数据管理(获取、重命名、删除、列表等)
188
+
189
+ **🔄 按类型分层服务**:
190
+ - **传统文件类型**(PDF、图片、视频等)→ `blob_service` 处理
191
+ - 核心操作:**上传** 和 **下载**
192
+ - 特点:二进制数据,重点是存储和传输
193
+
194
+ - **自定义文件类型**(在线表格、文档、画布等)→ 每种类型独立 `service`
195
+ - 核心操作:**创建** 和 **导出**
196
+ - 特点:结构化数据,重点是数据操作和格式转换
197
+
198
+ **🎯 设计优势**:
199
+ - **语义清晰**:不同类型的文件使用不同的操作语义,更符合实际使用场景
200
+ - **易于扩展**:新增自定义文件类型时,只需添加对应的独立服务
201
+ - **职责分离**:每个服务专注于特定类型的操作,代码更易维护
202
+ - **SDK 友好**:为 SDK 使用者提供更直观的 API 设计,而非通用的 REST API
203
+
204
+ #### 具体实现
205
+
206
+ - **file/**: 文件服务实现
207
+ - **blob_service**: 处理传统文件(二进制大对象)
208
+ - 支持多种上传模式(普通上传、流式上传、断点续传)
209
+ - 智能选择上传模式(根据文件大小)
210
+ - 生成上传/下载 URL
211
+ - 支持临时文件上传
212
+ - 适用类型:PDF、图片、视频、音频、压缩包等
213
+ - **file_service**: 处理文件元数据操作(所有类型通用)
214
+ - 获取、重命名、删除文件
215
+ - 列出文件
216
+ - 生成分享链接
217
+ - 记录文件访问
218
+ - **[future] document_service**: 在线文档服务(规划中)
219
+ - 创建文档、编辑内容、插入元素
220
+ - 导出为 Word、PDF、HTML 等格式
221
+ - **[future] canvas_service**: 画布服务(规划中)
222
+ - 创建画布、绘制图形、添加元素
223
+ - 导出为 PNG、SVG、PDF 等格式
224
+
225
+ - **folder/**: 文件夹服务实现
226
+ - 创建、重命名、移动、删除文件夹
227
+ - 列出文件夹内容
228
+
229
+ - **taple/**: Taple 电子表格服务实现(已上线)
230
+ - **taple_service**: 基础表格服务
231
+ - 创建表格、工作表、列、行、单元格
232
+ - 支持批量操作和乐观锁版本控制
233
+ - 合并单元格和视图管理
234
+ - **idempotent_taple_mixin**: 幂等性支持
235
+ - 自动管理幂等性键
236
+ - 防止重复操作
237
+
238
+ ### 数据模型 (`schemas/`)
239
+
240
+ - **file.py**: 文件相关的数据模型
241
+ - `File`: 文件信息
242
+ - `FileUploadResponse`: 文件上传响应
243
+ - `UploadUrlResponse`: URL上传响应
244
+ - `ShareLinkRequest`: 分享链接请求
245
+ - `FileListResponse`: 文件列表响应
246
+
247
+ - **folder.py**: 文件夹相关的数据模型
248
+ - `FolderInfo`: 文件夹信息
249
+ - `FolderListResponse`: 文件夹列表响应
250
+
251
+ - **context.py**: 上下文相关的数据模型
252
+ - `UserContext`: 用户上下文(组织、用户、角色、操作者)
253
+ - `RequestContext`: 请求上下文(请求ID、客户端信息、追踪信息)
254
+ - `FullContext`: 完整上下文
255
+
256
+ - **taple.py**: Taple 相关的数据模型
257
+ - `Table`: 表格信息
258
+ - `Sheet`: 工作表信息
259
+ - `Column`: 列信息
260
+ - `Row`: 行信息
261
+ - `Cell`: 单元格信息
262
+ - `ConflictInfo`: 冲突信息
263
+ - `BatchEditSheetResponse`: 批量编辑响应
264
+
265
+ ### 枚举定义 (`enums/`)
266
+
267
+ - **role.py**: 用户角色枚举(ACCOUNT、AGENT、SYSTEM)
268
+ - **upload_mode.py**: 上传模式枚举(NORMAL、STREAM、RESUMABLE)
269
+ - **export_format.py**: 导出格式枚举(XLSX、CSV、JSON、HTML、MARKDOWN)
270
+
271
+ ### 工具模块 (`utils/`)
272
+
273
+ - **file_utils.py**: 文件操作相关工具函数
274
+ - `get_file_mime_type`: 获取文件 MIME 类型(支持自定义映射)
275
+ - `split_file_chunks`: 文件分块
276
+ - `calculate_file_hash`: 计算文件哈希
277
+
278
+ - **converter.py**: 数据转换工具
279
+ - `timestamp_to_datetime`: 时间戳转换
280
+ - `convert_proto_to_model`: Proto 消息转模型
281
+
282
+ - **retry.py**: 提供重试装饰器 `retry_with_backoff`
283
+
284
+ - **upload_helper.py**: HTTP 上传辅助工具
285
+ - `AsyncHttpUploader`: 异步 HTTP 上传器
286
+ - `SyncHttpUploader`: 同步 HTTP 上传器
287
+ - 支持普通上传和断点续传
288
+
289
+ - **download_helper.py**: HTTP 下载辅助工具
290
+ - `AsyncHttpDownloader`: 异步 HTTP 下载器
291
+ - `SyncHttpDownloader`: 同步 HTTP 下载器
292
+ - 支持流式下载和断点续传
293
+
294
+ - **idempotency.py**: 幂等性支持工具
295
+ - `IdempotencyKeyGenerator`: 幂等性键生成器
296
+ - `IdempotencyManager`: 幂等性管理器
297
+ - `generate_idempotency_key`: 生成幂等性键函数
298
+
299
+ - **logging.py**: 日志配置和工具
300
+ - `GrpcJSONFormatter`: JSON 格式化器
301
+ - `GrpcRequestLogger`: gRPC 请求日志记录器
302
+ - 支持中文日志消息和图标
303
+
304
+ ### 错误处理 (`errors/`)
305
+
306
+ - **exceptions.py**: 定义了完整的异常体系
307
+ - `FileHubError`: 基础异常类
308
+ - `FileNotFoundError`: 文件不存在
309
+ - `FolderNotFoundError`: 文件夹不存在
310
+ - `UploadError`: 上传错误
311
+ - `DownloadError`: 下载错误
312
+ - `ValidationError`: 验证错误
313
+ - `ConnectionError`: 连接错误
314
+ - `TimeoutError`: 超时错误
315
+ - `PermissionError`: 权限错误
316
+ - 等等...
317
+
318
+ ## 安装
319
+
320
+ ```bash
321
+ pip install tamar-file-hub-client
322
+ ```
323
+
324
+ ## 配置
325
+
326
+ ### 环境变量配置
327
+
328
+ File Hub Client 支持通过环境变量配置连接参数,这在生产环境中特别有用。
329
+
330
+ 1. **创建 `.env` 文件**:
331
+ ```bash
332
+ # 在项目根目录创建 .env 文件
333
+ touch .env
334
+ ```
335
+
336
+ 2. **编辑 `.env` 文件**:
337
+
338
+ **线上环境示例(使用域名,不需要端口)**:
339
+ ```env
340
+ # gRPC 服务器配置 - 线上环境
341
+ FILE_HUB_HOST=api.filehub.example.com
342
+ # FILE_HUB_PORT 不设置,使用域名默认端口
343
+ FILE_HUB_SECURE=true
344
+ FILE_HUB_API_KEY=your-api-key
345
+
346
+ # 连接重试配置
347
+ FILE_HUB_RETRY_COUNT=5
348
+ FILE_HUB_RETRY_DELAY=2.0
349
+ ```
350
+
351
+ **本地开发环境示例(使用自定义端口)**:
352
+ ```env
353
+ # gRPC 服务器配置 - 本地开发
354
+ FILE_HUB_HOST=localhost
355
+ FILE_HUB_PORT=50051
356
+ FILE_HUB_SECURE=false
357
+ # FILE_HUB_API_KEY 本地开发可能不需要
358
+
359
+ # 连接重试配置
360
+ FILE_HUB_RETRY_COUNT=3
361
+ FILE_HUB_RETRY_DELAY=1.0
362
+ ```
363
+
364
+ 3. **支持的环境变量**:
365
+
366
+ | 环境变量 | 说明 | 默认值 |
367
+ |---------|------|--------|
368
+ | `FILE_HUB_HOST` | gRPC 服务器地址(域名或IP) | `localhost` |
369
+ | `FILE_HUB_PORT` | gRPC 服务器端口(可选,不设置时直接使用HOST) | 无 |
370
+ | `FILE_HUB_SECURE` | 是否启用 TLS/SSL | `false` |
371
+ | `FILE_HUB_API_KEY` | API 认证密钥(可选) | 无 |
372
+ | `FILE_HUB_RETRY_COUNT` | 连接重试次数 | `3` |
373
+ | `FILE_HUB_RETRY_DELAY` | 重试延迟(秒) | `1.0` |
374
+
375
+ ### TLS/SSL 配置
376
+
377
+ 当 `FILE_HUB_SECURE` 设置为 `true` 时,客户端会使用 TLS 加密连接:
378
+
379
+ - 默认使用系统的根证书
380
+ - 如果提供了 `FILE_HUB_API_KEY`,会自动添加到请求头中进行认证
381
+
382
+ ```python
383
+ # 通过代码配置 TLS
384
+ from file_hub_client import TamarFileHubClient
385
+
386
+ # 方式1:使用域名(不需要指定端口)
387
+ client = TamarFileHubClient(
388
+ host="secure-server.com", # 只需要域名
389
+ secure=True,
390
+ credentials={"api_key": "your-api-key"}
391
+ )
392
+
393
+ # 方式2:使用自定义端口
394
+ client = TamarFileHubClient(
395
+ host="secure-server.com",
396
+ port=8443, # 自定义端口
397
+ secure=True,
398
+ credentials={"api_key": "your-api-key"}
399
+ )
400
+ ```
401
+
402
+ ### 端口配置说明
403
+
404
+ 从 v0.0.3 版本开始,端口参数变为可选:
405
+
406
+ - **线上环境**:通常只需要提供域名,不需要指定端口
407
+ - **本地开发**:可以指定自定义端口
408
+
409
+ ```python
410
+ # 线上环境(使用标准端口)
411
+ client = TamarFileHubClient(
412
+ host="api.example.com", # 只提供域名
413
+ secure=True
414
+ )
415
+
416
+ # 本地开发(使用自定义端口)
417
+ client = TamarFileHubClient(
418
+ host="localhost",
419
+ port=50051, # 自定义端口
420
+ secure=False
421
+ )
422
+ ```
423
+
424
+ ### 连接重试
425
+
426
+ 客户端支持自动重试连接,对于不稳定的网络环境特别有用:
427
+
428
+ ```python
429
+ # 通过代码配置重试
430
+ from file_hub_client import TamarFileHubClient
431
+
432
+ client = TamarFileHubClient(
433
+ host="server.com",
434
+ retry_count=5, # 重试5次
435
+ retry_delay=2.0 # 每次重试间隔2秒
436
+ )
437
+ ```
438
+
439
+ ### 日志配置
440
+
441
+ File Hub Client 支持详细的 gRPC 请求日志记录:
442
+
443
+ ```python
444
+ from file_hub_client import AsyncTamarFileHubClient
445
+
446
+ # 启用日志记录(默认启用)
447
+ client = AsyncTamarFileHubClient(
448
+ enable_logging=True,
449
+ log_level="INFO" # DEBUG, INFO, WARNING, ERROR
450
+ )
451
+
452
+ # 日志输出示例(JSON格式):
453
+ # {
454
+ # "timestamp": "2025-07-15T17:30:00.123456",
455
+ # "level": "INFO",
456
+ # "type": "request",
457
+ # "uri": "CreateFolder",
458
+ # "request_id": "test-123",
459
+ # "data": {
460
+ # "folder_name": "测试文件夹",
461
+ # "parent_id": "parent-456"
462
+ # },
463
+ # "message": "📤 gRPC 请求: CreateFolder",
464
+ # "logger": "file_hub_client.grpc"
465
+ # }
466
+ ```
467
+
468
+ 日志类型包括:
469
+ - 📡 初始化日志
470
+ - 📤 请求日志(包含请求参数)
471
+ - ✅ 响应日志(包含耗时)
472
+ - ❌ 错误日志
473
+ - 🔗 连接成功
474
+ - ⚠️ 连接重试
475
+ - 👋 关闭连接
476
+
477
+ ### 加载环境变量
478
+
479
+ 使用 `python-dotenv` 加载 `.env` 文件(需要额外安装):
480
+
481
+ ```bash
482
+ pip install python-dotenv
483
+ ```
484
+
485
+ ```python
486
+ from dotenv import load_dotenv
487
+ import os
488
+
489
+ # 加载 .env 文件
490
+ load_dotenv()
491
+
492
+ # 现在可以直接使用客户端,它会自动读取环境变量
493
+ from file_hub_client import AsyncTamarFileHubClient
494
+
495
+ # 示例1:如果 FILE_HUB_PORT 未设置,将使用域名作为完整地址
496
+ # .env: FILE_HUB_HOST=api.example.com, FILE_HUB_SECURE=true
497
+ async with AsyncTamarFileHubClient() as client:
498
+ # 连接到 api.example.com(使用默认的 HTTPS 端口)
499
+ pass
500
+
501
+ # 示例2:如果 FILE_HUB_PORT 设置了,将使用 host:port 格式
502
+ # .env: FILE_HUB_HOST=localhost, FILE_HUB_PORT=50051
503
+ async with AsyncTamarFileHubClient() as client:
504
+ # 连接到 localhost:50051
505
+ pass
506
+ ```
507
+
508
+ ### 配置优先级
509
+
510
+ 客户端配置的优先级如下(从高到低):
511
+
512
+ 1. 直接传入的参数
513
+ 2. 环境变量
514
+ 3. 默认值
515
+
516
+ ```python
517
+ # 示例:参数会覆盖环境变量
518
+ from file_hub_client import AsyncTamarFileHubClient
519
+
520
+ # 情况1:覆盖环境变量中的 host
521
+ client = AsyncTamarFileHubClient(
522
+ host="override-host.com", # 这会覆盖 FILE_HUB_HOST
523
+ # port 将使用环境变量 FILE_HUB_PORT(如果设置了)
524
+ )
525
+
526
+ # 情况2:明确不使用端口(即使环境变量设置了端口)
527
+ client = AsyncTamarFileHubClient(
528
+ host="api.production.com",
529
+ port=None, # 明确指定不使用端口,忽略 FILE_HUB_PORT
530
+ secure=True
531
+ )
532
+ ```
533
+
534
+ ## 快速开始
535
+
536
+ ### 文件上传
537
+
538
+ File Hub Client 提供了统一的上传接口,支持多种上传模式:
539
+
540
+ #### 上传模式
541
+
542
+ - **NORMAL(普通模式)**:适用于小文件,通过 gRPC 直接上传
543
+ - **STREAM(流式上传)**:适用于流式数据上传
544
+ - **RESUMABLE(断点续传)**:支持断点续传,适用于大文件和不稳定网络
545
+
546
+ #### 最简单的上传
547
+
548
+ ```python
549
+ from file_hub_client import AsyncTamarFileHubClient
550
+
551
+ async with AsyncTamarFileHubClient() as client:
552
+ # 设置用户上下文
553
+ client.set_user_context(org_id="123", user_id="456")
554
+
555
+ # 最简单的用法 - 只需要文件路径
556
+ file_info = await client.blobs.upload(
557
+ "path/to/document.pdf",
558
+ folder_id="1dee0f7b-2e4f-45cd-a462-4e1d82df9bdd" # 上传到指定文件夹,不传则默认文件夹
559
+ )
560
+ print(f"上传成功: {file_info.file.id}")
561
+ print(f"文件类型: {file_info.file.file_type}") # 自动识别为 "pdf"
562
+ ```
563
+
564
+ #### 从URL上传文件
565
+
566
+ ```python
567
+ from file_hub_client import AsyncTamarFileHubClient
568
+
569
+ async with AsyncTamarFileHubClient() as client:
570
+ # 设置用户上下文
571
+ client.set_user_context(org_id="123", user_id="456")
572
+
573
+ # 从URL下载并上传文件(自动提取文件名)
574
+ file_info = await client.blobs.upload(
575
+ url="https://example.com/document.pdf"
576
+ )
577
+ print(f"上传成功: {file_info.file.id}")
578
+
579
+ # 从URL上传并指定文件名
580
+ file_info = await client.blobs.upload(
581
+ url="https://example.com/some-file",
582
+ file_name="my_document.pdf" # 指定文件名
583
+ )
584
+ print(f"文件名: {file_info.file.file_name}")
585
+ ```
586
+
587
+ #### 上传不同类型的内容
588
+
589
+ ```python
590
+ from file_hub_client import AsyncTamarFileHubClient
591
+ from pathlib import Path
592
+
593
+ async with AsyncTamarFileHubClient() as client:
594
+ # 1. 上传文件路径(字符串或Path对象)
595
+ file_info = await client.blobs.upload("path/to/file.pdf")
596
+ file_info = await client.blobs.upload(Path("path/to/file.pdf"))
597
+
598
+ # 2. 上传字节数据(需要指定文件名)
599
+ content = b"This is file content"
600
+ file_info = await client.blobs.upload(
601
+ content,
602
+ file_name="document.txt"
603
+ )
604
+
605
+ # 3. 上传文件对象
606
+ with open("image.png", "rb") as f:
607
+ file_info = await client.blobs.upload(f)
608
+ ```
609
+
610
+ #### 大文件上传(流式上传和断点续传)
611
+
612
+ ```python
613
+ from file_hub_client import AsyncTamarFileHubClient, UploadMode
614
+
615
+ async with AsyncTamarFileHubClient() as client:
616
+ # 设置用户上下文
617
+ client.set_user_context(org_id="123", user_id="456")
618
+
619
+ # 自动根据文件大小来选择是流式上传还是断点续传
620
+ file_info = await client.blobs.upload(
621
+ "large_video.mp4",
622
+ # mode=UploadMode.RESUMABLE # 也可以手动指定上传的模式
623
+ )
624
+ ```
625
+
626
+ #### 临时文件上传
627
+
628
+ ```python
629
+ from file_hub_client import AsyncTamarFileHubClient, UploadMode
630
+
631
+ async with AsyncTamarFileHubClient() as client:
632
+ # 设置用户上下文
633
+ client.set_user_context(org_id="123", user_id="456")
634
+
635
+ # 自动根据文件大小来选择是流式上传还是断点续传
636
+ file_info = await client.blobs.upload(
637
+ "large_video.mp4",
638
+ # mode=UploadMode.RESUMABLE, # 也可以手动指定上传的模式
639
+ is_temporary=True, # 由这个参数指定是否临时文件,是则不会纳入整个文件体系,即用户查询不到这个文件
640
+ # expire_seconds, # 过期秒数,默认30天
641
+ )
642
+ ```
643
+
644
+ ### 文件下载
645
+
646
+ File Hub Client 提供了统一的下载接口,支持两种结构返回:
647
+
648
+ #### 下载返回结构
649
+
650
+ - **保存到本地(本地路径)**:适用于各种文件,直接下载到本地,分块流式下载,支持重试和断点续传
651
+ - **保存到内存(bytes)**:适用于小文件,直接下载到内存,分块流式下载,支持重试
652
+
653
+ #### 下载到内存(适用于小文件)
654
+
655
+ ```python
656
+ from file_hub_client import AsyncTamarFileHubClient
657
+
658
+ async with AsyncTamarFileHubClient() as client:
659
+ # 设置用户上下文
660
+ client.set_user_context(org_id="123", user_id="456")
661
+
662
+ # 下载文件到内存(适用于小文件)
663
+ content = await client.blobs.download(file_id="file-001")
664
+ print(f"下载完成,文件大小: {len(content)} bytes")
665
+ ```
666
+
667
+ #### 下载到本地文件
668
+
669
+ ```python
670
+ from file_hub_client import AsyncTamarFileHubClient
671
+ from pathlib import Path
672
+
673
+ async with AsyncTamarFileHubClient() as client:
674
+ # 设置用户上下文
675
+ client.set_user_context(org_id="123", user_id="456")
676
+
677
+ # 下载文件到本地
678
+ save_path = await client.blobs.download(
679
+ file_id="file-001",
680
+ save_path="downloads/document.pdf" # 或 Path 对象
681
+ )
682
+ print(f"文件已保存到: {save_path}")
683
+ ```
684
+
685
+ ### 文件管理操作
686
+
687
+ File Hub Client 提供了完整的文件管理功能,通过 `files` 服务访问:
688
+
689
+ #### 获取文件信息
690
+
691
+ ```python
692
+ from file_hub_client import AsyncTamarFileHubClient
693
+
694
+ async with AsyncTamarFileHubClient() as client:
695
+ # 设置用户上下文
696
+ client.set_user_context(org_id="123", user_id="456")
697
+
698
+ # 获取文件详细信息
699
+ file_info = await client.files.get_file(file_id="file-001")
700
+ print(f"文件名: {file_info.file_name}")
701
+ print(f"文件大小: {file_info.file_size} bytes")
702
+ print(f"创建时间: {file_info.created_at}")
703
+ ```
704
+
705
+ #### 重命名文件
706
+
707
+ ```python
708
+ # 重命名文件
709
+ updated_file = await client.files.rename_file(
710
+ file_id="file-001",
711
+ new_name="新文档名称.pdf"
712
+ )
713
+ print(f"文件已重命名为: {updated_file.file_name}")
714
+ ```
715
+
716
+ #### 删除文件
717
+
718
+ ```python
719
+ # 删除文件
720
+ await client.files.delete_file(file_id="file-001")
721
+ print("文件已删除")
722
+ ```
723
+
724
+ #### 列出文件
725
+
726
+ ```python
727
+ # 列出文件夹中的文件
728
+ file_list = await client.files.list_files(
729
+ folder_id="folder-001", # 可选,不指定则列出根目录
730
+ file_name="report", # 可选,按名称过滤
731
+ file_type=["pdf", "docx"], # 可选,按类型过滤
732
+ page_size=20,
733
+ page=1
734
+ )
735
+
736
+ for file in file_list.files:
737
+ print(f"- {file.file_name} ({file.file_size} bytes)")
738
+ ```
739
+
740
+ #### 生成分享链接
741
+
742
+ ```python
743
+ # 生成文件分享链接
744
+ share_id = await client.files.generate_share_link(
745
+ file_id="file-001",
746
+ is_public=True, # 是否公开
747
+ access_scope="view", # 访问权限:view, download
748
+ expire_seconds=86400, # 24小时后过期
749
+ share_password="secret" # 可选,设置访问密码
750
+ )
751
+ print(f"分享ID: {share_id}")
752
+ ```
753
+
754
+ ### 文件夹操作
755
+
756
+ File Hub Client 提供了完整的文件夹管理功能,通过 `folders` 服务访问:
757
+
758
+ #### 创建文件夹
759
+
760
+ ```python
761
+ from file_hub_client import AsyncTamarFileHubClient
762
+
763
+ async with AsyncTamarFileHubClient() as client:
764
+ # 设置用户上下文
765
+ client.set_user_context(org_id="123", user_id="456")
766
+
767
+ # 在根目录创建文件夹
768
+ folder = await client.folders.create_folder(
769
+ folder_name="我的文档"
770
+ )
771
+ print(f"创建文件夹: {folder.id}")
772
+
773
+ # 在指定文件夹下创建子文件夹
774
+ sub_folder = await client.folders.create_folder(
775
+ folder_name="项目资料",
776
+ parent_id=folder.id
777
+ )
778
+ print(f"创建子文件夹: {sub_folder.id}")
779
+ ```
780
+
781
+ #### 重命名文件夹
782
+
783
+ ```python
784
+ # 重命名文件夹
785
+ updated_folder = await client.folders.rename_folder(
786
+ folder_id="folder-001",
787
+ new_name="新文件夹名称"
788
+ )
789
+ print(f"文件夹已重命名为: {updated_folder.folder_name}")
790
+ ```
791
+
792
+ #### 移动文件夹
793
+
794
+ ```python
795
+ # 移动文件夹到另一个文件夹下
796
+ moved_folder = await client.folders.move_folder(
797
+ folder_id="folder-001",
798
+ new_parent_id="folder-002" # 目标父文件夹ID
799
+ )
800
+ print(f"文件夹已移动到: {moved_folder.parent_id}")
801
+ ```
802
+
803
+ #### 删除文件夹
804
+
805
+ ```python
806
+ # 删除文件夹(包括其中的所有内容)
807
+ await client.folders.delete_folder(folder_id="folder-001")
808
+ print("文件夹已删除")
809
+ ```
810
+
811
+ #### 列出文件夹
812
+
813
+ ```python
814
+ # 列出根目录下的文件夹
815
+ folder_list = await client.folders.list_folders()
816
+
817
+ # 列出指定文件夹下的子文件夹
818
+ sub_folders = await client.folders.list_folders(
819
+ parent_id="folder-001",
820
+ folder_name="项目", # 可选,按名称过滤
821
+ )
822
+
823
+ for folder in folder_list.items:
824
+ print(f"- {folder.folder_name} (ID: {folder.id})")
825
+ print(f" 创建者: {folder.created_by}")
826
+ print(f" 创建时间: {folder.created_at}")
827
+ ```
828
+
829
+ #### 完整示例:组织文件结构
830
+
831
+ ```python
832
+ from file_hub_client import AsyncTamarFileHubClient
833
+
834
+ async with AsyncTamarFileHubClient() as client:
835
+ # 设置用户上下文
836
+ client.set_user_context(org_id="123", user_id="456")
837
+
838
+ # 创建项目文件夹结构
839
+ project_folder = await client.folders.create_folder("我的项目")
840
+ docs_folder = await client.folders.create_folder("文档", parent_id=project_folder.id)
841
+ images_folder = await client.folders.create_folder("图片", parent_id=project_folder.id)
842
+
843
+ # 上传文件到对应文件夹
844
+ doc_file = await client.blobs.upload(
845
+ "project_plan.pdf",
846
+ folder_id=docs_folder.id
847
+ )
848
+
849
+ image_file = await client.blobs.upload(
850
+ "logo.png",
851
+ folder_id=images_folder.id
852
+ )
853
+
854
+ # 列出项目文件夹的内容
855
+ print("项目结构:")
856
+
857
+ # 列出子文件夹
858
+ folders = await client.folders.list_folders(parent_id=project_folder.id)
859
+ for folder in folders.items:
860
+ print(f"📁 {folder.folder_name}/")
861
+
862
+ # 列出每个文件夹中的文件
863
+ files = await client.files.list_files(folder_id=folder.id)
864
+ for file in files.files:
865
+ print(f" 📄 {file.file_name}")
866
+ ```
867
+
868
+ ### Taple 电子表格操作
869
+
870
+ File Hub Client 提供了完整的类 Excel 电子表格功能,通过 `taples` 服务访问。支持表格、工作表、列、行、单元格的完整管理功能。
871
+
872
+ #### 基本操作
873
+
874
+ ```python
875
+ from file_hub_client import AsyncTamarFileHubClient
876
+
877
+ async with AsyncTamarFileHubClient() as client:
878
+ # 设置用户上下文
879
+ client.set_user_context(org_id="123", user_id="456")
880
+
881
+ # 创建表格
882
+ table = await client.taples.create_table(
883
+ name="员工信息表",
884
+ folder_id="folder-123", # 可选,不指定则使用默认文件夹
885
+ description="公司员工基本信息"
886
+ )
887
+
888
+ # 创建工作表
889
+ sheet = await client.taples.create_sheet(
890
+ table_id=table.table.id,
891
+ name="基本信息",
892
+ description="员工基本信息工作表"
893
+ )
894
+
895
+ # 获取表格信息
896
+ table_info = await client.taples.get_table(table_id=table.table.id)
897
+ # 或通过文件ID获取
898
+ # table_info = await client.taples.get_table(file_id="file-123")
899
+ ```
900
+
901
+ #### 列、行、单元格操作
902
+
903
+ ```python
904
+ async with AsyncTamarFileHubClient() as client:
905
+ client.set_user_context(org_id="123", user_id="456")
906
+
907
+ # 创建列(支持幂等性)
908
+ column = await client.taples.create_column(
909
+ sheet_id=sheet.sheet.id,
910
+ name="姓名",
911
+ column_type="text",
912
+ width=200,
913
+ idempotency_key="create-column-name-001"
914
+ )
915
+
916
+ # 更新列
917
+ updated_column = await client.taples.update_column(
918
+ sheet_id=sheet.sheet.id,
919
+ column_key=column.column.column_key,
920
+ name="员工姓名",
921
+ width=250,
922
+ hidden=False
923
+ )
924
+
925
+ # 创建行(支持幂等性)
926
+ row = await client.taples.create_row(
927
+ sheet_id=sheet.sheet.id,
928
+ position=0, # 可选,指定位置
929
+ height=30, # 可选,行高
930
+ hidden=False, # 可选,是否隐藏
931
+ idempotency_key="create-row-001"
932
+ )
933
+
934
+ # 编辑单元格(支持幂等性)
935
+ cell = await client.taples.edit_cell(
936
+ sheet_id=sheet.sheet.id,
937
+ column_key=column.column.column_key,
938
+ row_key=row.row.row_key,
939
+ raw_value="张三",
940
+ idempotency_key="edit-cell-001"
941
+ )
942
+
943
+ # 删除操作
944
+ await client.taples.delete_cell(sheet_id=sheet.sheet.id, column_key="col_1", row_key="row_1")
945
+ await client.taples.delete_row(sheet_id=sheet.sheet.id, row_key="row_1")
946
+ await client.taples.delete_column(sheet_id=sheet.sheet.id, column_key="col_1")
947
+ ```
948
+
949
+ #### 批量操作
950
+
951
+ ```python
952
+ # 批量编辑列
953
+ column_operations = [
954
+ {
955
+ "create": {
956
+ "name": "部门",
957
+ "column_type": "text",
958
+ "position": 1
959
+ }
960
+ },
961
+ {
962
+ "update": {
963
+ "column_key": "col_1",
964
+ "name": "新名称",
965
+ "width": 300
966
+ }
967
+ },
968
+ {
969
+ "delete": {
970
+ "column_key": "col_2"
971
+ }
972
+ }
973
+ ]
974
+
975
+ result = await client.taples.batch_edit_columns(
976
+ sheet_id=sheet.sheet.id,
977
+ operations=column_operations,
978
+ idempotency_key="batch-columns-001"
979
+ )
980
+
981
+ # 批量编辑行
982
+ row_operations = [
983
+ {
984
+ "create": {
985
+ "position": 0,
986
+ "height": 40
987
+ }
988
+ },
989
+ {
990
+ "update": {
991
+ "row_key": "row_1",
992
+ "height": 50,
993
+ "hidden": True
994
+ }
995
+ }
996
+ ]
997
+
998
+ result = await client.taples.batch_edit_rows(
999
+ sheet_id=sheet.sheet.id,
1000
+ operations=row_operations
1001
+ )
1002
+
1003
+ # 批量编辑单元格
1004
+ cell_operations = [
1005
+ {
1006
+ "edit": {
1007
+ "column_key": "col_1",
1008
+ "row_key": "row_1",
1009
+ "raw_value": "销售部"
1010
+ }
1011
+ },
1012
+ {
1013
+ "clear": {
1014
+ "column_key": "col_2",
1015
+ "row_key": "row_1"
1016
+ }
1017
+ }
1018
+ ]
1019
+
1020
+ result = await client.taples.batch_edit_cells(
1021
+ sheet_id=sheet.sheet.id,
1022
+ operations=cell_operations
1023
+ )
1024
+ ```
1025
+
1026
+ #### 数据获取
1027
+
1028
+ ```python
1029
+ # 获取工作表版本(轻量级)
1030
+ version_info = await client.taples.get_sheet_version(sheet_id=sheet.sheet.id)
1031
+ print(f"当前版本: {version_info.version}")
1032
+
1033
+ # 获取完整工作表数据
1034
+ sheet_data = await client.taples.get_sheet_data(
1035
+ sheet_id=sheet.sheet.id,
1036
+ version=100 # 可选,获取从该版本以来的变化
1037
+ )
1038
+
1039
+ # 获取列数据(包含该列所有单元格)
1040
+ column_data = await client.taples.get_column_data(
1041
+ sheet_id=sheet.sheet.id,
1042
+ column_key="col_1"
1043
+ )
1044
+
1045
+ # 获取行数据(包含该行所有单元格)
1046
+ row_data = await client.taples.get_row_data(
1047
+ sheet_id=sheet.sheet.id,
1048
+ row_key="row_1"
1049
+ )
1050
+
1051
+ # 获取单个单元格数据
1052
+ cell_data = await client.taples.get_cell_data(
1053
+ sheet_id=sheet.sheet.id,
1054
+ column_key="col_1",
1055
+ row_key="row_1"
1056
+ )
1057
+ ```
1058
+
1059
+ #### 版本控制和冲突处理
1060
+
1061
+ Taple 支持乐观锁版本控制,在并发编辑时自动处理版本冲突:
1062
+
1063
+ ```python
1064
+ # 方式1:自动获取版本(推荐)
1065
+ # SDK 会自动获取最新版本号
1066
+ column = await client.taples.create_column(
1067
+ sheet_id=sheet.sheet.id,
1068
+ name="自动版本",
1069
+ column_type="text"
1070
+ )
1071
+
1072
+ # 方式2:手动指定版本
1073
+ # 适用于需要精确控制的场景
1074
+ version_info = await client.taples.get_sheet_version(sheet_id=sheet.sheet.id)
1075
+ column = await client.taples.create_column(
1076
+ sheet_id=sheet.sheet.id,
1077
+ name="手动版本",
1078
+ column_type="text",
1079
+ sheet_version=version_info.version,
1080
+ client_id="my-client-123"
1081
+ )
1082
+
1083
+ # 批量操作时的版本控制
1084
+ operations = [...] # 你的操作列表
1085
+ batch_result = await client.taples.batch_edit_sheet(
1086
+ sheet_id=sheet.sheet.id,
1087
+ operations=operations,
1088
+ sheet_version=version_info.version,
1089
+ client_id="my-client-123"
1090
+ )
1091
+
1092
+ # 检查冲突
1093
+ if batch_result.conflict_info and batch_result.conflict_info.has_conflict:
1094
+ print(f"版本冲突: {batch_result.conflict_info.conflict_type}")
1095
+ print(f"服务器版本: {batch_result.conflict_info.server_version}")
1096
+ ```
1097
+
1098
+ #### 数据导入
1099
+
1100
+ Taple 支持从 CSV、Excel 等文件导入数据:
1101
+
1102
+ ```python
1103
+ import tempfile
1104
+ import csv
1105
+
1106
+ # 创建测试 CSV 文件
1107
+ def create_test_csv():
1108
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.csv', delete=False, encoding='utf-8')
1109
+ writer = csv.writer(temp_file)
1110
+ writer.writerow(['姓名', '年龄', '部门', '薪资'])
1111
+ writer.writerow(['张三', '28', '技术部', '15000'])
1112
+ writer.writerow(['李四', '32', '销售部', '12000'])
1113
+ temp_file.close()
1114
+ return temp_file.name
1115
+
1116
+ # 上传 CSV 文件
1117
+ csv_file = create_test_csv()
1118
+ upload_result = await client.blobs.upload(csv_file, folder_id=folder_id)
1119
+
1120
+ # 导入到表格
1121
+ import_result = await client.taples.import_table_data(
1122
+ table_id=table.table.id,
1123
+ file_id=upload_result.file.id,
1124
+ import_mode="append", # append 或 overwrite
1125
+ sheet_index=0, # 导入到第几个工作表
1126
+ has_header=True, # 第一行是否为表头
1127
+ idempotency_key="import-csv-001"
1128
+ )
1129
+
1130
+ if import_result.success:
1131
+ print(f"导入成功!")
1132
+ print(f"导入了 {import_result.rows_imported} 行数据")
1133
+ print(f"创建了 {import_result.columns_created} 列")
1134
+ else:
1135
+ print(f"导入失败: {import_result.error_message}")
1136
+ ```
1137
+
1138
+ #### 数据导出
1139
+
1140
+ Taple 支持导出为多种格式:
1141
+
1142
+ ```python
1143
+ from file_hub_client.enums import ExportFormat
1144
+
1145
+ # 导出为 Excel
1146
+ export_result = await client.taples.export_table_data(
1147
+ table_id=table.table.id,
1148
+ format=ExportFormat.XLSX,
1149
+ options={
1150
+ "include_formulas": True,
1151
+ "include_styles": True,
1152
+ "include_hidden_sheets": False,
1153
+ "include_hidden_rows_cols": False
1154
+ },
1155
+ idempotency_key="export-excel-001"
1156
+ )
1157
+
1158
+ if export_result.success:
1159
+ print(f"导出成功!")
1160
+ print(f"文件ID: {export_result.file_id}")
1161
+ print(f"文件名: {export_result.file_name}")
1162
+ print(f"下载链接: {export_result.download_url}")
1163
+
1164
+ # 下载导出的文件
1165
+ await client.blobs.download(
1166
+ file_id=export_result.file_id,
1167
+ save_path=f"exports/{export_result.file_name}"
1168
+ )
1169
+
1170
+ # 支持的导出格式
1171
+ # - ExportFormat.XLSX: Excel 格式
1172
+ # - ExportFormat.CSV: CSV 格式(多工作表会生成 ZIP)
1173
+ # - ExportFormat.JSON: JSON 格式
1174
+ # - ExportFormat.HTML: HTML 表格
1175
+ # - ExportFormat.MARKDOWN: Markdown 表格
1176
+ ```
1177
+
1178
+ #### 表格克隆操作
1179
+
1180
+ Taple 支持将表格数据克隆到另一个组织,包括所有工作表、列、行和单元格数据:
1181
+
1182
+ ```python
1183
+ from file_hub_client import AsyncTamarFileHubClient
1184
+ import uuid
1185
+
1186
+ async def clone_table_example():
1187
+ async with AsyncTamarFileHubClient() as client:
1188
+ client.set_user_context(org_id="source-org-123", user_id="456")
1189
+
1190
+ # 克隆表格到另一个组织
1191
+ clone_result = await client.taples.clone_table_data(
1192
+ source_table_id="table-123",
1193
+ target_org_id="target-org-456",
1194
+ target_user_id="target-user-789",
1195
+ target_folder_id="target-folder-001", # 可选,目标文件夹
1196
+ new_table_name="克隆的员工表_2024", # 可选,新表格名称
1197
+ include_views=False, # 是否包含视图数据
1198
+ idempotency_key=str(uuid.uuid4()) # 幂等性键
1199
+ )
1200
+
1201
+ if clone_result.success:
1202
+ print(f"克隆成功!")
1203
+ print(f"新表格ID: {clone_result.new_table_id}")
1204
+ print(f"新文件ID: {clone_result.new_file_id}")
1205
+ print(f"克隆了 {clone_result.sheets_cloned} 个工作表")
1206
+ print(f"克隆了 {clone_result.cells_cloned} 个单元格")
1207
+ print(f"创建时间: {clone_result.created_at}")
1208
+ else:
1209
+ print(f"克隆失败: {clone_result.error_message}")
1210
+
1211
+ # 运行示例
1212
+ import asyncio
1213
+ asyncio.run(clone_table_example())
1214
+ ```
1215
+
1216
+ **同步客户端示例**:
1217
+
1218
+ ```python
1219
+ from file_hub_client import TamarFileHubClient
1220
+ import uuid
1221
+
1222
+ with TamarFileHubClient() as client:
1223
+ client.set_user_context(org_id="source-org-123", user_id="456")
1224
+
1225
+ # 克隆表格(最简示例)
1226
+ clone_result = client.taples.clone_table_data(
1227
+ source_table_id="table-123",
1228
+ target_org_id="target-org-456",
1229
+ target_user_id="target-user-789"
1230
+ # 其他参数都是可选的
1231
+ )
1232
+
1233
+ print(f"克隆结果: {clone_result.success}")
1234
+ if clone_result.success:
1235
+ print(f"新表格ID: {clone_result.new_table_id}")
1236
+ ```
1237
+
1238
+ **克隆操作特点**:
1239
+
1240
+ - **跨组织克隆**:可以将表格从一个组织克隆到另一个组织
1241
+ - **完整数据复制**:包括表格结构、工作表、列定义、行数据和单元格内容
1242
+ - **可选视图数据**:通过 `include_views` 参数控制是否包含视图数据(默认不包含)
1243
+ - **灵活命名**:可自定义新表格名称,不指定则自动使用原名称+Copy后缀
1244
+ - **目标位置控制**:可指定目标文件夹,不指定则使用目标用户的默认文件夹
1245
+ - **幂等性支持**:支持幂等性键,避免重复克隆
1246
+
1247
+ #### 完整示例:创建和填充数据表
1248
+
1249
+ ```python
1250
+ from file_hub_client import AsyncTamarFileHubClient
1251
+ from datetime import datetime
1252
+ import uuid
1253
+
1254
+ async def create_employee_table():
1255
+ async with AsyncTamarFileHubClient() as client:
1256
+ client.set_user_context(org_id="123", user_id="456")
1257
+
1258
+ # 1. 创建表格
1259
+ table = await client.taples.create_table(
1260
+ name=f"员工信息_{datetime.now().strftime('%Y%m%d')}",
1261
+ description="员工基本信息管理表",
1262
+ idempotency_key=str(uuid.uuid4())
1263
+ )
1264
+
1265
+ # 2. 创建工作表
1266
+ sheet = await client.taples.create_sheet(
1267
+ table_id=table.table.id,
1268
+ name="花名册",
1269
+ description="员工花名册"
1270
+ )
1271
+
1272
+ # 3. 批量创建列
1273
+ column_operations = [
1274
+ {"create": {"name": "工号", "column_type": "text", "position": 0}},
1275
+ {"create": {"name": "姓名", "column_type": "text", "position": 1}},
1276
+ {"create": {"name": "部门", "column_type": "text", "position": 2}},
1277
+ {"create": {"name": "入职日期", "column_type": "date", "position": 3}},
1278
+ {"create": {"name": "薪资", "column_type": "number", "position": 4}}
1279
+ ]
1280
+
1281
+ columns_result = await client.taples.batch_edit_columns(
1282
+ sheet_id=sheet.sheet.id,
1283
+ operations=column_operations
1284
+ )
1285
+
1286
+ # 4. 批量创建行并填充数据
1287
+ employees = [
1288
+ {"工号": "E001", "姓名": "张三", "部门": "技术部", "入职日期": "2023-01-15", "薪资": "15000"},
1289
+ {"工号": "E002", "姓名": "李四", "部门": "销售部", "入职日期": "2023-03-20", "薪资": "12000"},
1290
+ {"工号": "E003", "姓名": "王五", "部门": "市场部", "入职日期": "2023-06-10", "薪资": "13000"}
1291
+ ]
1292
+
1293
+ # 创建行
1294
+ row_operations = [{"create": {"position": i}} for i in range(len(employees))]
1295
+ rows_result = await client.taples.batch_edit_rows(
1296
+ sheet_id=sheet.sheet.id,
1297
+ operations=row_operations
1298
+ )
1299
+
1300
+ # 填充数据
1301
+ cell_operations = []
1302
+ for i, (row, employee) in enumerate(zip(rows_result['results'], employees)):
1303
+ if row['success'] and row['row']:
1304
+ row_key = row['row'].row_key
1305
+ for j, (col, (field, value)) in enumerate(zip(columns_result['results'], employee.items())):
1306
+ if col['success'] and col['column']:
1307
+ cell_operations.append({
1308
+ "edit": {
1309
+ "column_key": col['column'].column_key,
1310
+ "row_key": row_key,
1311
+ "raw_value": value
1312
+ }
1313
+ })
1314
+
1315
+ # 批量更新单元格
1316
+ await client.taples.batch_edit_cells(
1317
+ sheet_id=sheet.sheet.id,
1318
+ operations=cell_operations
1319
+ )
1320
+
1321
+ print(f"表格创建成功!")
1322
+ print(f"表格ID: {table.table.id}")
1323
+ print(f"工作表ID: {sheet.sheet.id}")
1324
+
1325
+ # 5. 读取数据验证
1326
+ sheet_data = await client.taples.get_sheet_data(sheet_id=sheet.sheet.id)
1327
+ print(f"数据行数: {len(sheet_data.rows)}")
1328
+ print(f"数据列数: {len(sheet_data.columns)}")
1329
+
1330
+ # 运行示例
1331
+ import asyncio
1332
+ asyncio.run(create_employee_table())
1333
+ ```
1334
+
1335
+ #### 高级功能:合并单元格
1336
+
1337
+ ```python
1338
+ # 合并单元格
1339
+ merge_result = await client.taples.merge_cells(
1340
+ sheet_id=sheet.sheet.id,
1341
+ start_row_key="row_1",
1342
+ end_row_key="row_3",
1343
+ start_column_key="col_1",
1344
+ end_column_key="col_2",
1345
+ idempotency_key="merge-cells-001"
1346
+ )
1347
+
1348
+ # 取消合并
1349
+ unmerge_result = await client.taples.unmerge_cells(
1350
+ sheet_id=sheet.sheet.id,
1351
+ merged_cell_id=merge_result.merged_cell.id,
1352
+ idempotency_key="unmerge-cells-001"
1353
+ )
1354
+
1355
+ # 获取合并单元格信息
1356
+ merged_cells = await client.taples.list_merged_cells(sheet_id=sheet.sheet.id)
1357
+ for cell in merged_cells.merged_cells:
1358
+ print(f"合并区域: {cell.start_row_key}-{cell.end_row_key}, {cell.start_column_key}-{cell.end_column_key}")
1359
+ ```
1360
+
1361
+ #### 高级功能:表格视图
1362
+
1363
+ ```python
1364
+ # 创建视图
1365
+ view = await client.taples.create_table_view(
1366
+ table_id=table.table.id,
1367
+ name="销售部视图",
1368
+ description="只显示销售部数据",
1369
+ filter_config={
1370
+ "conditions": [
1371
+ {
1372
+ "column_key": "col_dept",
1373
+ "operator": "equals",
1374
+ "value": "销售部"
1375
+ }
1376
+ ]
1377
+ },
1378
+ sort_config={
1379
+ "rules": [
1380
+ {
1381
+ "column_key": "col_salary",
1382
+ "order": "desc"
1383
+ }
1384
+ ]
1385
+ },
1386
+ visible_columns=["col_name", "col_dept", "col_salary"],
1387
+ idempotency_key="create-view-001"
1388
+ )
1389
+
1390
+ # 获取视图列表
1391
+ views = await client.taples.list_table_views(table_id=table.table.id)
1392
+ for v in views.views:
1393
+ print(f"视图: {v.name} - {v.description}")
1394
+
1395
+ # 使用视图获取数据
1396
+ view_data = await client.taples.get_table_view_data(
1397
+ view_id=view.view.id,
1398
+ page_size=20,
1399
+ page=1
1400
+ )
1401
+ ```
1402
+
1403
+ #### 同步客户端示例
1404
+
1405
+ 所有异步操作都有对应的同步版本:
1406
+
1407
+ ```python
1408
+ from file_hub_client import TamarFileHubClient
1409
+
1410
+ with TamarFileHubClient() as client:
1411
+ client.set_user_context(org_id="123", user_id="456")
1412
+
1413
+ # 创建表格
1414
+ table = client.taples.create_table(
1415
+ name="销售数据",
1416
+ description="2024年销售数据"
1417
+ )
1418
+
1419
+ # 创建工作表
1420
+ sheet = client.taples.create_sheet(
1421
+ table_id=table.table.id,
1422
+ name="Q1数据"
1423
+ )
1424
+
1425
+ # 创建列
1426
+ column = client.taples.create_column(
1427
+ sheet_id=sheet.sheet.id,
1428
+ name="产品名称",
1429
+ column_type="text"
1430
+ )
1431
+
1432
+ print(f"创建成功: {table.table.id}")
1433
+ ```
1434
+
1435
+ ### 最简单的使用方式(推荐)
1436
+
1437
+ File Hub Client 提供了预配置的单例客户端,可以直接导入使用:
1438
+
1439
+ ```python
1440
+ # 同步客户端
1441
+ import os
1442
+ from file_hub_client import tamar_client as client
1443
+
1444
+ # 直接使用,无需 with 语句
1445
+ client.set_user_context(org_id="123", user_id="456")
1446
+ file_path = os.path.abspath("1.jpg")
1447
+ file_info = client.blobs.upload(file_path)
1448
+ ```
1449
+
1450
+ ```python
1451
+ # 异步客户端
1452
+ import asyncio
1453
+ import os
1454
+ from file_hub_client import async_tamar_client as async_client
1455
+
1456
+
1457
+ async def main():
1458
+ # 直接使用,无需 with 语句
1459
+ await async_client._ensure_connected() # 需要手动连接
1460
+ async_client.set_user_context(org_id="123", user_id="456")
1461
+ file_path = os.path.abspath("1.jpg")
1462
+ file_info = await async_client.blobs.upload(file_path)
1463
+ print(f"上传成功: {file_info.file.id}")
1464
+
1465
+
1466
+ asyncio.run(main())
1467
+ ```
1468
+
1469
+ ### 自定义配置的单例
1470
+
1471
+ 如果需要自定义配置,可以使用 `get_client()` 或 `get_async_client()`:
1472
+
1473
+ ```python
1474
+ from file_hub_client import get_client
1475
+
1476
+ # 获取自定义配置的客户端(单例)
1477
+ client = get_client(
1478
+ host="custom-server.com",
1479
+ port=50051,
1480
+ secure=True
1481
+ )
1482
+ ```
1483
+
1484
+ ### 使用上下文管理器(可选)
1485
+
1486
+ 如果您希望明确控制连接的生命周期,仍然可以使用上下文管理器:
1487
+
1488
+ ```python
1489
+ import os
1490
+ from file_hub_client import TamarFileHubClient
1491
+
1492
+ # 使用 with 语句
1493
+ with TamarFileHubClient(host="localhost", port=50051) as client:
1494
+ file_path = os.path.abspath("1.jpg")
1495
+ file_info = client.blobs.upload(file_path)
1496
+ ```
1497
+
1498
+ ### 异步客户端示例
1499
+
1500
+ ```python
1501
+ import asyncio
1502
+ import os
1503
+ from file_hub_client import AsyncTamarFileHubClient
1504
+
1505
+
1506
+ async def main():
1507
+ # 创建客户端
1508
+ async with AsyncTamarFileHubClient(host="localhost", port=50051) as client:
1509
+ # 上传文件
1510
+ file_path = os.path.abspath("1.jpg")
1511
+ file_info = await client.blobs.upload(file_path)
1512
+ print(f"上传成功: {file_info.file.id}")
1513
+
1514
+
1515
+ asyncio.run(main())
1516
+ ```
1517
+
1518
+ ### 同步客户端示例
1519
+
1520
+ ```python
1521
+ import os
1522
+ from file_hub_client import TamarFileHubClient
1523
+
1524
+ # 创建客户端
1525
+ with TamarFileHubClient(host="localhost", port=50051) as client:
1526
+ # 上传文件
1527
+ file_path = os.path.abspath("1.jpg")
1528
+ file_info = client.blobs.upload(file_path)
1529
+ print(f"上传成功: {file_info.file.id}")
1530
+ ```
1531
+
1532
+ ### 使用用户上下文
1533
+
1534
+ File Hub Client 支持精细的用户上下文管理,区分资源所有权和实际操作者:
1535
+
1536
+ ```python
1537
+ import os
1538
+ from file_hub_client import AsyncTamarFileHubClient, UserContext, RequestContext, Role
1539
+
1540
+ # 创建用户上下文
1541
+ user_context = UserContext(
1542
+ org_id="org-123", # 组织ID
1543
+ user_id="user-456", # 用户ID(资源所有者)
1544
+ role=Role.ACCOUNT, # 角色:ACCOUNT, AGENT, SYSTEM
1545
+ actor_id="agent-789" # 实际操作者ID(可选,默认为user_id)
1546
+ )
1547
+
1548
+ # 创建请求上下文(自动收集客户端信息)
1549
+ request_context = RequestContext(
1550
+ client_ip="192.168.1.100", # 客户端IP(可选)
1551
+ client_type="web", # 客户端类型:web, mobile, desktop, cli
1552
+ client_version="2.0.0", # 客户端版本
1553
+ extra={"session_id": "xyz"} # 额外的元数据
1554
+ )
1555
+
1556
+ # 使用上下文创建客户端
1557
+ async with AsyncTamarFileHubClient(
1558
+ user_context=user_context,
1559
+ request_context=request_context
1560
+ ) as client:
1561
+ # 所有操作都会包含上下文信息
1562
+ file_path = os.path.abspath("1.jpg")
1563
+ await client.blobs.upload(file_path)
1564
+ ```
1565
+
1566
+ ### 动态切换用户上下文
1567
+
1568
+ ```python
1569
+ from file_hub_client import tamar_client as client, Role
1570
+
1571
+ # 初始用户
1572
+ client.set_user_context(
1573
+ org_id="123",
1574
+ user_id="456",
1575
+ role=Role.ACCOUNT
1576
+ )
1577
+ ```
1578
+
1579
+ ### 请求追踪
1580
+
1581
+ 客户端会自动生成请求ID并收集环境信息:
1582
+
1583
+ ```python
1584
+ from file_hub_client import tamar_client as client
1585
+
1586
+ # 获取当前上下文信息
1587
+ user_ctx = client.get_user_context()
1588
+ request_ctx = client.get_request_context()
1589
+
1590
+ print(f"请求ID: {request_ctx.request_id}")
1591
+ print(f"客户端信息: {request_ctx.client_type} v{request_ctx.client_version}")
1592
+ print(f"操作者: {user_ctx.actor_id} (角色: {user_ctx.role})")
1593
+ ```
1594
+
1595
+ ### 显式请求ID控制
1596
+
1597
+ 所有服务方法都支持显式传入 `request_id` 参数,用于更精确的请求追踪和调试:
1598
+
1599
+ ```python
1600
+ from file_hub_client import AsyncTamarFileHubClient
1601
+ import uuid
1602
+
1603
+ # 创建客户端
1604
+ client = AsyncTamarFileHubClient(user_context=user_context)
1605
+
1606
+ # 方式1:不传入request_id,系统自动生成
1607
+ table = await client.taples.create_table(name="auto_request_id_table")
1608
+
1609
+ # 方式2:传入自定义request_id
1610
+ custom_request_id = f"create-table-{uuid.uuid4().hex}"
1611
+ table = await client.taples.create_table(
1612
+ name="custom_request_id_table",
1613
+ request_id=custom_request_id
1614
+ )
1615
+
1616
+ # 方式3:使用业务相关的request_id
1617
+ business_request_id = "user-action-2025-0714-001"
1618
+ folder = await client.folders.create_folder(
1619
+ folder_name="important_folder",
1620
+ request_id=business_request_id
1621
+ )
1622
+
1623
+ # 同步客户端同样支持
1624
+ sync_client = TamarFileHubClient(user_context=user_context)
1625
+ file_info = sync_client.files.get_file(
1626
+ file_id="file-123",
1627
+ request_id="debug-get-file-001"
1628
+ )
1629
+ ```
1630
+
1631
+ #### 请求ID优先级
1632
+
1633
+ 请求ID的使用优先级如下:
1634
+
1635
+ 1. **显式传入的 request_id 参数**(最高优先级)
1636
+ 2. **RequestContext 中的 request_id**
1637
+ 3. **自动生成的 UUID**(默认行为)
1638
+
1639
+ ```python
1640
+ # 优先级示例
1641
+ request_context = RequestContext(
1642
+ extra={"request_id": "context-request-id-123"}
1643
+ )
1644
+
1645
+ client = AsyncTamarFileHubClient(
1646
+ user_context=user_context,
1647
+ request_context=request_context
1648
+ )
1649
+
1650
+ # 使用显式传入的request_id(优先级最高)
1651
+ await client.taples.create_table(
1652
+ name="explicit_priority",
1653
+ request_id="explicit-request-id-456"
1654
+ )
1655
+ # 实际使用:explicit-request-id-456
1656
+
1657
+ # 使用RequestContext中的request_id
1658
+ await client.taples.create_table(name="context_priority")
1659
+ # 实际使用:context-request-id-123
1660
+
1661
+ # 如果都没有,自动生成UUID
1662
+ minimal_client = AsyncTamarFileHubClient(user_context=user_context)
1663
+ await minimal_client.taples.create_table(name="auto_generated")
1664
+ # 实际使用:自动生成的UUID
1665
+ ```
1666
+
1667
+ #### 支持request_id的服务方法
1668
+
1669
+ 所有服务方法都支持 `request_id` 参数:
1670
+
1671
+ **Taple 服务**:
1672
+ - `create_table()`, `get_table()`, `delete_table()`
1673
+ - `create_sheet()`, `get_sheet()`, `delete_sheet()`
1674
+ - `create_column()`, `update_column()`, `delete_column()`
1675
+ - `create_row()`, `update_row()`, `delete_row()`
1676
+ - `edit_cell()`, `clear_cell()`, `get_cell_data()`
1677
+ - `import_table_data()`, `export_table_data()`
1678
+ - `clone_table_data()`
1679
+ - 所有批量操作方法
1680
+
1681
+ **文件服务**:
1682
+ - `get_file()`, `rename_file()`, `delete_file()`, `list_files()`
1683
+ - `generate_share_link()`, `visit_file()`
1684
+
1685
+ **文件夹服务**:
1686
+ - `create_folder()`, `rename_folder()`, `move_folder()`, `delete_folder()`
1687
+ - `list_folders()`
1688
+
1689
+ **Blob 服务**:
1690
+ - `upload()`, `download()`, `generate_upload_url()`, `generate_download_url()`
1691
+
1692
+ #### 请求追踪最佳实践
1693
+
1694
+ 1. **业务操作使用有意义的request_id**:
1695
+ ```python
1696
+ # 用户触发的操作
1697
+ request_id = f"user-{user_id}-create-table-{int(time.time())}"
1698
+
1699
+ # 定时任务
1700
+ request_id = f"cron-export-{datetime.now().strftime('%Y%m%d_%H%M%S')}"
1701
+
1702
+ # 批量操作
1703
+ request_id = f"batch-import-{batch_id}"
1704
+ ```
1705
+
1706
+ 2. **调试时使用描述性request_id**:
1707
+ ```python
1708
+ # 调试特定功能
1709
+ request_id = "debug-column-creation-issue"
1710
+
1711
+ # 性能测试
1712
+ request_id = f"perf-test-{operation_name}-{iteration}"
1713
+ ```
1714
+
1715
+ 3. **生产环境保持简洁**:
1716
+ ```python
1717
+ # 生产环境可以使用简短的标识符
1718
+ request_id = f"prod-{uuid.uuid4().hex[:8]}"
1719
+ ```
1720
+
1721
+ ## 高级功能
1722
+
1723
+ ### 幂等性支持
1724
+
1725
+ 许多操作支持幂等性,通过 `idempotency_key` 参数防止重复操作:
1726
+
1727
+ ```python
1728
+ from file_hub_client import AsyncTamarFileHubClient, generate_idempotency_key
1729
+
1730
+ async with AsyncTamarFileHubClient() as client:
1731
+ # 自动生成幂等性键
1732
+ key = generate_idempotency_key("create", "table", "employee_2024")
1733
+
1734
+ # 使用幂等性键创建表格
1735
+ # 即使多次调用,也只会创建一次
1736
+ table = await client.taples.create_table(
1737
+ name="员工表_2024",
1738
+ idempotency_key=key
1739
+ )
1740
+
1741
+ # 使用 IdempotencyManager 管理多个操作
1742
+ from file_hub_client import IdempotencyManager
1743
+
1744
+ manager = IdempotencyManager(prefix="import_batch_001")
1745
+
1746
+ # 批量导入,每个操作都有唯一的幂等性键
1747
+ for i, file_id in enumerate(file_ids):
1748
+ await client.taples.import_table_data(
1749
+ table_id=table.table.id,
1750
+ file_id=file_id,
1751
+ idempotency_key=manager.generate_key("import", str(i))
1752
+ )
1753
+ ```
1754
+
1755
+ ### 错误重试机制
1756
+
1757
+ SDK 内置了智能的错误重试机制:
1758
+
1759
+ ```python
1760
+ from file_hub_client import AsyncTamarFileHubClient
1761
+
1762
+ # 配置重试策略
1763
+ client = AsyncTamarFileHubClient(
1764
+ retry_count=5, # 最大重试次数
1765
+ retry_delay=1.0 # 初始重试延迟(秒)
1766
+ )
1767
+
1768
+ # 使用装饰器自定义重试逻辑
1769
+ from file_hub_client.utils.retry import retry_with_backoff
1770
+
1771
+ @retry_with_backoff(max_retries=3, base_delay=0.5)
1772
+ async def upload_with_retry(client, file_path):
1773
+ return await client.blobs.upload(file_path)
1774
+ ```
1775
+
1776
+ ### 批量操作优化
1777
+
1778
+ 对于大量数据操作,使用批量接口可以显著提高性能:
1779
+
1780
+ ```python
1781
+ # 批量操作示例
1782
+ async def batch_import_data(client, sheet_id, data_rows):
1783
+ # 分批处理,每批100行
1784
+ batch_size = 100
1785
+
1786
+ for i in range(0, len(data_rows), batch_size):
1787
+ batch = data_rows[i:i + batch_size]
1788
+
1789
+ # 创建批量操作
1790
+ operations = []
1791
+ for row_data in batch:
1792
+ operations.append({
1793
+ "create": {"position": i}
1794
+ })
1795
+
1796
+ # 执行批量创建
1797
+ result = await client.taples.batch_edit_rows(
1798
+ sheet_id=sheet_id,
1799
+ operations=operations
1800
+ )
1801
+
1802
+ if not result.get('success'):
1803
+ print(f"批次 {i//batch_size + 1} 失败: {result.get('error_message')}")
1804
+ continue
1805
+
1806
+ # 批量填充单元格数据
1807
+ cell_operations = []
1808
+ # ... 构建单元格操作
1809
+
1810
+ await client.taples.batch_edit_cells(
1811
+ sheet_id=sheet_id,
1812
+ operations=cell_operations
1813
+ )
1814
+ ```
1815
+
1816
+ ### 并发控制
1817
+
1818
+ 使用异步客户端时,可以充分利用并发提高效率:
1819
+
1820
+ ```python
1821
+ import asyncio
1822
+ from file_hub_client import AsyncTamarFileHubClient
1823
+
1824
+ async def concurrent_uploads(file_paths):
1825
+ async with AsyncTamarFileHubClient() as client:
1826
+ client.set_user_context(org_id="123", user_id="456")
1827
+
1828
+ # 并发上传多个文件
1829
+ tasks = []
1830
+ for file_path in file_paths:
1831
+ task = client.blobs.upload(file_path)
1832
+ tasks.append(task)
1833
+
1834
+ # 等待所有上传完成
1835
+ results = await asyncio.gather(*tasks, return_exceptions=True)
1836
+
1837
+ # 处理结果
1838
+ for i, result in enumerate(results):
1839
+ if isinstance(result, Exception):
1840
+ print(f"文件 {file_paths[i]} 上传失败: {result}")
1841
+ else:
1842
+ print(f"文件 {file_paths[i]} 上传成功: {result.file.id}")
1843
+
1844
+ # 使用信号量限制并发数
1845
+ async def controlled_concurrent_operations(items, max_concurrent=10):
1846
+ semaphore = asyncio.Semaphore(max_concurrent)
1847
+
1848
+ async def process_item(item):
1849
+ async with semaphore:
1850
+ # 处理单个项目
1851
+ return await some_operation(item)
1852
+
1853
+ tasks = [process_item(item) for item in items]
1854
+ return await asyncio.gather(*tasks)
1855
+ ```
1856
+
1857
+ ### 流式处理大数据
1858
+
1859
+ 对于大量数据的处理,使用流式API避免内存溢出:
1860
+
1861
+ ```python
1862
+ # 流式读取大型表格数据
1863
+ async def stream_table_data(client, sheet_id):
1864
+ page = 1
1865
+ page_size = 1000
1866
+
1867
+ while True:
1868
+ # 分页获取数据
1869
+ result = await client.taples.get_sheet_data(
1870
+ sheet_id=sheet_id,
1871
+ page=page,
1872
+ page_size=page_size
1873
+ )
1874
+
1875
+ if not result.rows:
1876
+ break
1877
+
1878
+ # 处理当前页数据
1879
+ for row in result.rows:
1880
+ yield row
1881
+
1882
+ page += 1
1883
+
1884
+ # 使用示例
1885
+ async def process_large_table():
1886
+ async with AsyncTamarFileHubClient() as client:
1887
+ async for row in stream_table_data(client, "sheet-123"):
1888
+ # 处理每一行数据
1889
+ process_row(row)
1890
+ ```
1891
+
1892
+ ## 开发
1893
+
1894
+ ### 生成 gRPC 代码
1895
+
1896
+ 当 proto 文件更新后,需要重新生成代码:
1897
+
1898
+ ```bash
1899
+ # 使用命令行工具
1900
+ file-hub-gen-proto
1901
+
1902
+ # 或直接运行脚本
1903
+ cd file_hub_client/rpc
1904
+ python generate_grpc.py
1905
+ ```
1906
+
1907
+ ### 运行测试
1908
+
1909
+ ```bash
1910
+ # 运行所有测试
1911
+ python tests/taple/run_all_tests.py
1912
+
1913
+ # 运行特定测试
1914
+ python tests/taple/test_table_operations.py
1915
+
1916
+ # 设置测试环境变量
1917
+ export TEST_SERVER_HOST=your-test-server.com
1918
+ export TEST_SERVER_PORT=50051
1919
+ export TEST_ORG_ID=test-org-123
1920
+ export TEST_USER_ID=test-user-456
1921
+ ```
1922
+
1923
+ ## 故障排除
1924
+
1925
+ ### 常见问题
1926
+
1927
+ 1. **连接超时**
1928
+ ```python
1929
+ # 增加超时时间
1930
+ client = AsyncTamarFileHubClient(
1931
+ retry_count=5,
1932
+ retry_delay=2.0
1933
+ )
1934
+ ```
1935
+
1936
+ 2. **版本冲突**
1937
+ ```python
1938
+ # 自动重试版本冲突
1939
+ while True:
1940
+ try:
1941
+ result = await client.taples.create_column(...)
1942
+ break
1943
+ except VersionConflictError:
1944
+ # 重新获取版本并重试
1945
+ continue
1946
+ ```
1947
+
1948
+ 3. **大文件上传失败**
1949
+ ```python
1950
+ # 使用断点续传模式
1951
+ file_info = await client.blobs.upload(
1952
+ "large_file.zip",
1953
+ mode=UploadMode.RESUMABLE
1954
+ )
1955
+ ```
1956
+
1957
+ ### 调试技巧
1958
+
1959
+ 1. **启用详细日志**
1960
+ ```python
1961
+ client = AsyncTamarFileHubClient(
1962
+ enable_logging=True,
1963
+ log_level="DEBUG"
1964
+ )
1965
+ ```
1966
+
1967
+ 2. **使用请求ID追踪**
1968
+ ```python
1969
+ # 为每个操作设置唯一的请求ID
1970
+ request_id = f"debug-{operation}-{timestamp}"
1971
+ result = await client.taples.create_table(
1972
+ name="test",
1973
+ request_id=request_id
1974
+ )
1975
+ ```
1976
+
1977
+ 3. **检查网络连接**
1978
+ ```python
1979
+ # 测试连接
1980
+ try:
1981
+ await client.connect()
1982
+ print("连接成功")
1983
+ except ConnectionError as e:
1984
+ print(f"连接失败: {e}")
1985
+ ```
1986
+
1987
+ ## 最佳实践
1988
+
1989
+ 1. **使用单例客户端**:避免频繁创建客户端实例
1990
+ 2. **设置合理的超时和重试**:根据网络环境调整
1991
+ 3. **使用幂等性键**:防止重复操作
1992
+ 4. **批量操作**:提高性能
1993
+ 5. **错误处理**:妥善处理各种异常
1994
+ 6. **资源清理**:使用 with 语句确保资源释放
1995
+ 7. **并发控制**:合理使用并发避免服务器过载
1996
+
1997
+ ## 许可证
1998
+
1999
+ MIT License
2000
+
2001
+ ## 贡献
2002
+
2003
+ 欢迎提交 Issue 和 Pull Request!
2004
+
2005
+ ## 更新日志
2006
+
2007
+ ### v0.0.4 (2025-01)
2008
+ - 新增从URL上传文件功能
2009
+ - 支持自动下载URL内容并上传到GCS
2010
+ - 支持自定义文件名
2011
+ - 修复URL上传时的MIME类型检测问题
2012
+ - 改进测试中对哈希去重的说明
2013
+
2014
+ ### v0.0.3 (2025-07)
2015
+ - 端口参数变为可选,支持直接使用域名连接
2016
+ - 优化环境变量端口配置处理
2017
+ - 改进连接地址构建逻辑
2018
+
2019
+ ### v1.5.0 (2025-01)
2020
+ - 添加 gRPC 请求自动日志记录
2021
+ - 支持 JSON 格式日志输出
2022
+ - 日志消息中文化并添加图标
2023
+ - 优化 CSV 文件 MIME 类型检测
2024
+ - 修复拦截器类型错误问题
2025
+
2026
+ ### v1.4.0 (2024-12)
2027
+ - 添加 Taple 表格导入导出功能
2028
+ - 支持表格克隆操作
2029
+ - 优化批量操作性能
2030
+ - 增强幂等性支持
2031
+
2032
+ ### v1.3.0 (2024-11)
2033
+ - 添加完整的 Taple 电子表格支持
2034
+ - 实现乐观锁版本控制
2035
+ - 支持合并单元格和视图管理
2036
+
2037
+ ### v1.2.0 (2024-10)
2038
+ - 重构服务架构,实现分层设计
2039
+ - 添加请求ID追踪功能
2040
+ - 增强用户上下文管理
2041
+
2042
+ ### v1.1.0 (2024-09)
2043
+ - 添加 TLS/SSL 支持
2044
+ - 实现自动重试机制
2045
+ - 优化大文件上传下载
2046
+
2047
+ ### v1.0.0 (2024-08)
2048
+ - 初始版本发布
2049
+ - 基础文件和文件夹操作
2050
+ - 异步和同步双客户端支持