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.
- tamar_file_hub_client-0.0.2/PKG-INFO +2050 -0
- tamar_file_hub_client-0.0.2/README.md +2004 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/__init__.py +39 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/client.py +43 -6
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/async_client.py +91 -11
- tamar_file_hub_client-0.0.2/file_hub_client/rpc/gen/taple_service_pb2.py +225 -0
- tamar_file_hub_client-0.0.2/file_hub_client/rpc/gen/taple_service_pb2_grpc.py +1626 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/generate_grpc.py +2 -2
- tamar_file_hub_client-0.0.2/file_hub_client/rpc/interceptors.py +550 -0
- tamar_file_hub_client-0.0.2/file_hub_client/rpc/protos/taple_service.proto +874 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/sync_client.py +91 -9
- tamar_file_hub_client-0.0.2/file_hub_client/schemas/__init__.py +103 -0
- tamar_file_hub_client-0.0.2/file_hub_client/schemas/taple.py +413 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/__init__.py +5 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- tamar_file_hub_client-0.0.2/file_hub_client/services/taple/__init__.py +10 -0
- tamar_file_hub_client-0.0.2/file_hub_client/services/taple/async_taple_service.py +2281 -0
- tamar_file_hub_client-0.0.2/file_hub_client/services/taple/base_taple_service.py +353 -0
- tamar_file_hub_client-0.0.2/file_hub_client/services/taple/idempotent_taple_mixin.py +142 -0
- tamar_file_hub_client-0.0.2/file_hub_client/services/taple/sync_taple_service.py +2256 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/__init__.py +90 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/file_utils.py +153 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/idempotency.py +196 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/logging.py +315 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/retry.py +308 -0
- tamar_file_hub_client-0.0.2/file_hub_client/utils/smart_retry.py +403 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/setup.py +4 -2
- tamar_file_hub_client-0.0.2/tamar_file_hub_client.egg-info/PKG-INFO +2050 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/SOURCES.txt +13 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/tamar_file_hub_client.egg-info/requires.txt +6 -0
- tamar_file_hub_client-0.0.1/PKG-INFO +0 -874
- tamar_file_hub_client-0.0.1/README.md +0 -830
- tamar_file_hub_client-0.0.1/file_hub_client/schemas/__init__.py +0 -43
- tamar_file_hub_client-0.0.1/file_hub_client/utils/__init__.py +0 -48
- tamar_file_hub_client-0.0.1/file_hub_client/utils/file_utils.py +0 -105
- tamar_file_hub_client-0.0.1/file_hub_client/utils/retry.py +0 -69
- tamar_file_hub_client-0.0.1/tamar_file_hub_client.egg-info/PKG-INFO +0 -874
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/MANIFEST.in +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/__init__.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/export_format.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/role.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/enums/upload_mode.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/errors/__init__.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/errors/exceptions.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/py.typed +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/__init__.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/gen/__init__.py +0 -0
- {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
- {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
- {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
- {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
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/protos/file_service.proto +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/rpc/protos/folder_service.proto +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/context.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/file.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/schemas/folder.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/file/__init__.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/services/folder/__init__.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/converter.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/download_helper.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/file_hub_client/utils/upload_helper.py +0 -0
- {tamar_file_hub_client-0.0.1 → tamar_file_hub_client-0.0.2}/setup.cfg +0 -0
- {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
- {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
|
+
- 异步和同步双客户端支持
|