py-wisharetec-toolkit 1.0.0__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.
- py_wisharetec_toolkit-1.0.0/LICENSE +21 -0
- py_wisharetec_toolkit-1.0.0/PKG-INFO +250 -0
- py_wisharetec_toolkit-1.0.0/README.md +194 -0
- py_wisharetec_toolkit-1.0.0/pyproject.toml +48 -0
- py_wisharetec_toolkit-1.0.0/setup.cfg +4 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit/__init__.py +17 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit/saas/__init__.py +523 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit/saas/utils.py +47 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit.egg-info/PKG-INFO +250 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit.egg-info/SOURCES.txt +11 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit.egg-info/dependency_links.txt +1 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit.egg-info/requires.txt +13 -0
- py_wisharetec_toolkit-1.0.0/src/py_wisharetec_toolkit.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 郭磊
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-wisharetec-toolkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
5
|
+
Author-email: Guolei <174000902@qq.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 郭磊
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://gitee.com/guolei19850528/py_wisharetec_toolkit
|
|
29
|
+
Project-URL: Repository, https://gitee.com/guolei19850528/py_wisharetec_toolkit.git
|
|
30
|
+
Keywords: wisharetec,python,client,api,wisharetec saas,saas
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Operating System :: OS Independent
|
|
40
|
+
Requires-Python: >=3.10
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: httpx>=0.27.0
|
|
44
|
+
Requires-Dist: pydantic>=2.0
|
|
45
|
+
Requires-Dist: jsonpath-ng>=1.5.3
|
|
46
|
+
Requires-Dist: jsonschema>=4.21.0
|
|
47
|
+
Requires-Dist: diskcache>=5.6.3
|
|
48
|
+
Requires-Dist: redis>=4.6.0
|
|
49
|
+
Requires-Dist: py-httpx-toolkit>=1.0.0
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
52
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
53
|
+
Requires-Dist: setuptools>=61.0; extra == "dev"
|
|
54
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# py-wisharetec-toolkit
|
|
58
|
+
|
|
59
|
+
Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
60
|
+
|
|
61
|
+
## 功能特性
|
|
62
|
+
|
|
63
|
+
- 封装 SaaS 系统的认证机制,包括登录、令牌刷新和令牌缓存
|
|
64
|
+
- 提供同步和异步两种 HTTP 请求模式
|
|
65
|
+
- 集成 JSON Schema 校验和 JSONPath 查询工具
|
|
66
|
+
- 支持 diskcache 和 Redis 两种缓存后端
|
|
67
|
+
|
|
68
|
+
## 安装
|
|
69
|
+
|
|
70
|
+
### 使用 uv(推荐)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv add py-wisharetec-toolkit
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 使用 pip
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install py-wisharetec-toolkit
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 依赖包
|
|
83
|
+
|
|
84
|
+
| 依赖包 | 版本要求 | 说明 |
|
|
85
|
+
|--------|----------|------|
|
|
86
|
+
| httpx | >=0.27.0 | HTTP 客户端库,支持同步和异步请求 |
|
|
87
|
+
| pydantic | >=2.0 | 数据验证和序列化 |
|
|
88
|
+
| jsonpath-ng | >=1.5.3 | JSONPath 查询工具 |
|
|
89
|
+
| jsonschema | >=4.21.0 | JSON Schema 校验 |
|
|
90
|
+
| diskcache | >=5.6.3 | 本地磁盘缓存 |
|
|
91
|
+
| redis | >=4.6.0 | Redis 缓存支持 |
|
|
92
|
+
| py-httpx-toolkit | >=1.0.0 | HTTP 请求工具封装 |
|
|
93
|
+
|
|
94
|
+
## API 说明
|
|
95
|
+
|
|
96
|
+
### Saas 类
|
|
97
|
+
|
|
98
|
+
#### 初始化
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
102
|
+
|
|
103
|
+
saas = Saas(
|
|
104
|
+
base_url="https://saas.wisharetec.com/",
|
|
105
|
+
account="your_account",
|
|
106
|
+
password="your_password",
|
|
107
|
+
cache_config={
|
|
108
|
+
"instance": None, # diskcache.Cache 或 redis.Redis 实例
|
|
109
|
+
"key": "py_wisharetec_saas_account",
|
|
110
|
+
"expire": 7100,
|
|
111
|
+
},
|
|
112
|
+
client_kwargs={
|
|
113
|
+
"timeout": 60,
|
|
114
|
+
"verify": False,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### 同步方法
|
|
120
|
+
|
|
121
|
+
| 方法 | 说明 |
|
|
122
|
+
|------|------|
|
|
123
|
+
| `login()` | 用户登录,获取认证 Token |
|
|
124
|
+
| `refresh_token()` | 智能刷新 Token(支持缓存) |
|
|
125
|
+
| `query_space_manage_tree()` | 查询空间管理树结构 |
|
|
126
|
+
| `request_with_token()` | 发送带授权头的请求 |
|
|
127
|
+
|
|
128
|
+
#### 异步方法
|
|
129
|
+
|
|
130
|
+
| 方法 | 说明 |
|
|
131
|
+
|------|------|
|
|
132
|
+
| `async_login()` | 异步用户登录 |
|
|
133
|
+
| `async_refresh_token()` | 异步智能刷新 Token |
|
|
134
|
+
| `async_query_space_manage_tree()` | 异步查询空间管理树 |
|
|
135
|
+
| `async_request_with_token()` | 异步发送带授权头的请求 |
|
|
136
|
+
|
|
137
|
+
## utils 工具函数
|
|
138
|
+
|
|
139
|
+
### json_find_first
|
|
140
|
+
|
|
141
|
+
使用 JSONPath 表达式从 JSON 数据中查找第一个匹配项。
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from py_wisharetec_toolkit.saas.utils import json_find_first
|
|
145
|
+
|
|
146
|
+
data = {"userInfoV": {"id": "123"}}
|
|
147
|
+
result = json_find_first("$.userInfoV.id", data)
|
|
148
|
+
# 返回: "123"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### json_is_valid
|
|
152
|
+
|
|
153
|
+
校验 JSON 数据是否符合指定的 JSON Schema。
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from py_wisharetec_toolkit.saas.utils import json_is_valid
|
|
157
|
+
|
|
158
|
+
schema = {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"properties": {"id": {"type": "string"}},
|
|
161
|
+
"required": ["id"]
|
|
162
|
+
}
|
|
163
|
+
data = {"id": "123"}
|
|
164
|
+
result = json_is_valid(schema, data)
|
|
165
|
+
# 返回: True
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 使用示例
|
|
169
|
+
|
|
170
|
+
### 同步方式
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
174
|
+
|
|
175
|
+
saas = Saas(
|
|
176
|
+
account="test",
|
|
177
|
+
password="password"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# 登录获取 Token
|
|
181
|
+
saas.login()
|
|
182
|
+
|
|
183
|
+
# 发送请求
|
|
184
|
+
response = saas.request_with_token(
|
|
185
|
+
method="GET",
|
|
186
|
+
url="/api/data/list"
|
|
187
|
+
)
|
|
188
|
+
print(response.json())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 异步方式
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import asyncio
|
|
195
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
196
|
+
|
|
197
|
+
async def main():
|
|
198
|
+
saas = Saas(
|
|
199
|
+
account="test",
|
|
200
|
+
password="password"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# 异步登录
|
|
204
|
+
await saas.async_login()
|
|
205
|
+
|
|
206
|
+
# 异步发送请求
|
|
207
|
+
response = await saas.async_request_with_token(
|
|
208
|
+
method="GET",
|
|
209
|
+
url="/api/data/list"
|
|
210
|
+
)
|
|
211
|
+
print(response.json())
|
|
212
|
+
|
|
213
|
+
asyncio.run(main())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 使用缓存
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import diskcache
|
|
220
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
221
|
+
|
|
222
|
+
cache = diskcache.Cache("./cache")
|
|
223
|
+
|
|
224
|
+
saas = Saas(
|
|
225
|
+
account="test",
|
|
226
|
+
password="password",
|
|
227
|
+
cache_config={
|
|
228
|
+
"instance": cache,
|
|
229
|
+
"key": "saas_token",
|
|
230
|
+
"expire": 7100
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# 自动从缓存获取或刷新 Token
|
|
235
|
+
saas.refresh_token()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 作者
|
|
239
|
+
|
|
240
|
+
**Guolei**
|
|
241
|
+
|
|
242
|
+
- 邮箱: 174000902@qq.com
|
|
243
|
+
|
|
244
|
+
## 项目主页
|
|
245
|
+
|
|
246
|
+
- **Gitee**: [https://gitee.com/guolei19850528/py_wisharetec_toolkit](https://gitee.com/guolei19850528/py_wisharetec_toolkit)
|
|
247
|
+
|
|
248
|
+
## 许可证
|
|
249
|
+
|
|
250
|
+
MIT License
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# py-wisharetec-toolkit
|
|
2
|
+
|
|
3
|
+
Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 封装 SaaS 系统的认证机制,包括登录、令牌刷新和令牌缓存
|
|
8
|
+
- 提供同步和异步两种 HTTP 请求模式
|
|
9
|
+
- 集成 JSON Schema 校验和 JSONPath 查询工具
|
|
10
|
+
- 支持 diskcache 和 Redis 两种缓存后端
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
### 使用 uv(推荐)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv add py-wisharetec-toolkit
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 使用 pip
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install py-wisharetec-toolkit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 依赖包
|
|
27
|
+
|
|
28
|
+
| 依赖包 | 版本要求 | 说明 |
|
|
29
|
+
|--------|----------|------|
|
|
30
|
+
| httpx | >=0.27.0 | HTTP 客户端库,支持同步和异步请求 |
|
|
31
|
+
| pydantic | >=2.0 | 数据验证和序列化 |
|
|
32
|
+
| jsonpath-ng | >=1.5.3 | JSONPath 查询工具 |
|
|
33
|
+
| jsonschema | >=4.21.0 | JSON Schema 校验 |
|
|
34
|
+
| diskcache | >=5.6.3 | 本地磁盘缓存 |
|
|
35
|
+
| redis | >=4.6.0 | Redis 缓存支持 |
|
|
36
|
+
| py-httpx-toolkit | >=1.0.0 | HTTP 请求工具封装 |
|
|
37
|
+
|
|
38
|
+
## API 说明
|
|
39
|
+
|
|
40
|
+
### Saas 类
|
|
41
|
+
|
|
42
|
+
#### 初始化
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
46
|
+
|
|
47
|
+
saas = Saas(
|
|
48
|
+
base_url="https://saas.wisharetec.com/",
|
|
49
|
+
account="your_account",
|
|
50
|
+
password="your_password",
|
|
51
|
+
cache_config={
|
|
52
|
+
"instance": None, # diskcache.Cache 或 redis.Redis 实例
|
|
53
|
+
"key": "py_wisharetec_saas_account",
|
|
54
|
+
"expire": 7100,
|
|
55
|
+
},
|
|
56
|
+
client_kwargs={
|
|
57
|
+
"timeout": 60,
|
|
58
|
+
"verify": False,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### 同步方法
|
|
64
|
+
|
|
65
|
+
| 方法 | 说明 |
|
|
66
|
+
|------|------|
|
|
67
|
+
| `login()` | 用户登录,获取认证 Token |
|
|
68
|
+
| `refresh_token()` | 智能刷新 Token(支持缓存) |
|
|
69
|
+
| `query_space_manage_tree()` | 查询空间管理树结构 |
|
|
70
|
+
| `request_with_token()` | 发送带授权头的请求 |
|
|
71
|
+
|
|
72
|
+
#### 异步方法
|
|
73
|
+
|
|
74
|
+
| 方法 | 说明 |
|
|
75
|
+
|------|------|
|
|
76
|
+
| `async_login()` | 异步用户登录 |
|
|
77
|
+
| `async_refresh_token()` | 异步智能刷新 Token |
|
|
78
|
+
| `async_query_space_manage_tree()` | 异步查询空间管理树 |
|
|
79
|
+
| `async_request_with_token()` | 异步发送带授权头的请求 |
|
|
80
|
+
|
|
81
|
+
## utils 工具函数
|
|
82
|
+
|
|
83
|
+
### json_find_first
|
|
84
|
+
|
|
85
|
+
使用 JSONPath 表达式从 JSON 数据中查找第一个匹配项。
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from py_wisharetec_toolkit.saas.utils import json_find_first
|
|
89
|
+
|
|
90
|
+
data = {"userInfoV": {"id": "123"}}
|
|
91
|
+
result = json_find_first("$.userInfoV.id", data)
|
|
92
|
+
# 返回: "123"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### json_is_valid
|
|
96
|
+
|
|
97
|
+
校验 JSON 数据是否符合指定的 JSON Schema。
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from py_wisharetec_toolkit.saas.utils import json_is_valid
|
|
101
|
+
|
|
102
|
+
schema = {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {"id": {"type": "string"}},
|
|
105
|
+
"required": ["id"]
|
|
106
|
+
}
|
|
107
|
+
data = {"id": "123"}
|
|
108
|
+
result = json_is_valid(schema, data)
|
|
109
|
+
# 返回: True
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 使用示例
|
|
113
|
+
|
|
114
|
+
### 同步方式
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
118
|
+
|
|
119
|
+
saas = Saas(
|
|
120
|
+
account="test",
|
|
121
|
+
password="password"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# 登录获取 Token
|
|
125
|
+
saas.login()
|
|
126
|
+
|
|
127
|
+
# 发送请求
|
|
128
|
+
response = saas.request_with_token(
|
|
129
|
+
method="GET",
|
|
130
|
+
url="/api/data/list"
|
|
131
|
+
)
|
|
132
|
+
print(response.json())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 异步方式
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import asyncio
|
|
139
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
140
|
+
|
|
141
|
+
async def main():
|
|
142
|
+
saas = Saas(
|
|
143
|
+
account="test",
|
|
144
|
+
password="password"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# 异步登录
|
|
148
|
+
await saas.async_login()
|
|
149
|
+
|
|
150
|
+
# 异步发送请求
|
|
151
|
+
response = await saas.async_request_with_token(
|
|
152
|
+
method="GET",
|
|
153
|
+
url="/api/data/list"
|
|
154
|
+
)
|
|
155
|
+
print(response.json())
|
|
156
|
+
|
|
157
|
+
asyncio.run(main())
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 使用缓存
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
import diskcache
|
|
164
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
165
|
+
|
|
166
|
+
cache = diskcache.Cache("./cache")
|
|
167
|
+
|
|
168
|
+
saas = Saas(
|
|
169
|
+
account="test",
|
|
170
|
+
password="password",
|
|
171
|
+
cache_config={
|
|
172
|
+
"instance": cache,
|
|
173
|
+
"key": "saas_token",
|
|
174
|
+
"expire": 7100
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# 自动从缓存获取或刷新 Token
|
|
179
|
+
saas.refresh_token()
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 作者
|
|
183
|
+
|
|
184
|
+
**Guolei**
|
|
185
|
+
|
|
186
|
+
- 邮箱: 174000902@qq.com
|
|
187
|
+
|
|
188
|
+
## 项目主页
|
|
189
|
+
|
|
190
|
+
- **Gitee**: [https://gitee.com/guolei19850528/py_wisharetec_toolkit](https://gitee.com/guolei19850528/py_wisharetec_toolkit)
|
|
191
|
+
|
|
192
|
+
## 许可证
|
|
193
|
+
|
|
194
|
+
MIT License
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
[project]
|
|
5
|
+
name = "py-wisharetec-toolkit"
|
|
6
|
+
version = "1.0.0"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Guolei", email = "174000902@qq.com" }
|
|
10
|
+
]
|
|
11
|
+
description = "Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。"
|
|
12
|
+
keywords = ["wisharetec", "python", "client", "api", "wisharetec saas", "saas"]
|
|
13
|
+
license = { file = "LICENSE" }
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"httpx>=0.27.0",
|
|
28
|
+
"pydantic>=2.0",
|
|
29
|
+
"jsonpath-ng>=1.5.3",
|
|
30
|
+
"jsonschema>=4.21.0",
|
|
31
|
+
"diskcache>=5.6.3",
|
|
32
|
+
"redis>=4.6.0",
|
|
33
|
+
"py-httpx-toolkit>=1.0.0",
|
|
34
|
+
]
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=7.0",
|
|
38
|
+
"pytest-cov>=4.0",
|
|
39
|
+
"setuptools>=61.0",
|
|
40
|
+
"twine>=4.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
"Homepage" = "https://gitee.com/guolei19850528/py_wisharetec_toolkit"
|
|
45
|
+
"Repository" = "https://gitee.com/guolei19850528/py_wisharetec_toolkit.git"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
py_wisharetec 包入口模块
|
|
5
|
+
|
|
6
|
+
Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
7
|
+
|
|
8
|
+
主要功能:
|
|
9
|
+
- 封装 SaaS 系统的认证机制,包括登录、令牌刷新和令牌缓存
|
|
10
|
+
- 提供同步和异步两种 HTTP 请求模式
|
|
11
|
+
- 集成 JSON Schema 校验和 JSONPath 查询工具
|
|
12
|
+
- 支持 diskcache 和 Redis 两种缓存后端
|
|
13
|
+
|
|
14
|
+
子模块说明:
|
|
15
|
+
- saas: 核心 SaaS 客户端实现,包含 Saas 类和工具函数
|
|
16
|
+
- saas.utils: 工具函数模块,提供 JSON 数据处理相关功能
|
|
17
|
+
"""
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
import hashlib
|
|
4
|
+
from typing import Optional, Union
|
|
5
|
+
|
|
6
|
+
import diskcache
|
|
7
|
+
import httpx
|
|
8
|
+
import redis
|
|
9
|
+
from py_httpx_toolkit import Httpx
|
|
10
|
+
|
|
11
|
+
from .utils import json_is_valid
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Saas(Httpx):
|
|
15
|
+
"""
|
|
16
|
+
SaaS系统客户端类,继承自Httpx,提供与Wisharetec SaaS平台的交互能力
|
|
17
|
+
|
|
18
|
+
主要功能包括:
|
|
19
|
+
- 用户登录认证
|
|
20
|
+
- Token管理与自动刷新
|
|
21
|
+
- 请求授权头自动添加
|
|
22
|
+
- 支持同步和异步请求
|
|
23
|
+
- 支持缓存Token(Redis/diskcache)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
base_url: Optional[str] = "https://saas.wisharetec.com/",
|
|
29
|
+
account: Optional[str] = None,
|
|
30
|
+
password: Optional[str] = None,
|
|
31
|
+
cache_config: Optional[dict] = None,
|
|
32
|
+
client_kwargs: Optional[dict] = None
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
初始化SaaS客户端
|
|
36
|
+
|
|
37
|
+
:param base_url: SaaS平台基础URL,默认为"https://saas.wisharetec.com/"
|
|
38
|
+
:param account: 用户账号
|
|
39
|
+
:param password: 用户密码(明文,内部会进行MD5加密)
|
|
40
|
+
:param cache_config: 缓存配置字典,支持以下键:
|
|
41
|
+
- instance: 缓存实例(diskcache.Cache或redis.Redis),为None时不使用缓存
|
|
42
|
+
- key: 缓存键名,默认为"py_wisharetec_saas_{account}"
|
|
43
|
+
- expire: 缓存过期时间(秒),默认为7100秒
|
|
44
|
+
:param client_kwargs: httpx客户端配置参数,会覆盖默认配置
|
|
45
|
+
"""
|
|
46
|
+
# 处理基础URL,移除末尾斜杠保证URL格式统一
|
|
47
|
+
self.base_url = base_url or "https://saas.wisharetec.com/"
|
|
48
|
+
self.base_url = self.base_url[:-1] if self.base_url.endswith("/") else self.base_url
|
|
49
|
+
|
|
50
|
+
# 存储账号密码(密码在请求时进行MD5加密)
|
|
51
|
+
self.account = account or ""
|
|
52
|
+
self.password = password or ""
|
|
53
|
+
|
|
54
|
+
# 配置缓存参数,使用默认值与用户配置合并
|
|
55
|
+
self.cache_config = cache_config or {}
|
|
56
|
+
self.cache_config = {
|
|
57
|
+
**{
|
|
58
|
+
"instance": None, # 缓存实例
|
|
59
|
+
"key": f"py_wisharetec_saas_{self.account}", # 缓存键
|
|
60
|
+
"expire": 7100, # 过期时间(秒)
|
|
61
|
+
},
|
|
62
|
+
**self.cache_config,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# 配置httpx客户端参数,使用默认值与用户配置合并
|
|
66
|
+
self.client_kwargs = client_kwargs or {}
|
|
67
|
+
self.client_kwargs = {
|
|
68
|
+
**{
|
|
69
|
+
"base_url": self.base_url, # 基础URL
|
|
70
|
+
"timeout": 60, # 请求超时时间(秒)
|
|
71
|
+
"verify": False, # 禁用SSL证书验证
|
|
72
|
+
"headers": {
|
|
73
|
+
"client": "co-pc" # 客户端标识
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
**self.client_kwargs,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# 初始化token为空,需要通过login或refresh_token获取
|
|
80
|
+
self.token = ""
|
|
81
|
+
|
|
82
|
+
def query_space_manage_tree(
|
|
83
|
+
self,
|
|
84
|
+
client: Optional[httpx.Client] = None,
|
|
85
|
+
client_kwargs: Optional[dict] = None,
|
|
86
|
+
**kwargs
|
|
87
|
+
) -> httpx.Response:
|
|
88
|
+
"""
|
|
89
|
+
查询空间管理树结构
|
|
90
|
+
|
|
91
|
+
该方法用于获取当前用户有权限访问的空间管理树数据,常用于验证Token是否有效。
|
|
92
|
+
请求会携带账号和MD5加密后的密码进行身份验证。
|
|
93
|
+
|
|
94
|
+
:param client: 可选的同步httpx.Client实例,若未提供则使用默认客户端
|
|
95
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
96
|
+
:param kwargs: 额外的请求参数,可覆盖默认配置(如method、url、headers等)
|
|
97
|
+
:return: httpx.Response - HTTP响应对象,成功时包含空间管理树数据
|
|
98
|
+
"""
|
|
99
|
+
# 处理参数默认值
|
|
100
|
+
client = client or None
|
|
101
|
+
client_kwargs = client_kwargs or {}
|
|
102
|
+
kwargs = kwargs or {}
|
|
103
|
+
|
|
104
|
+
# 构建请求参数,合并默认配置与用户传入的配置
|
|
105
|
+
kwargs = {
|
|
106
|
+
**{
|
|
107
|
+
"method": "GET",
|
|
108
|
+
"url": "/api/space/space/manageTree",
|
|
109
|
+
"headers": {
|
|
110
|
+
"client": "co-pc",
|
|
111
|
+
"authorization": self.token,
|
|
112
|
+
},
|
|
113
|
+
"json": {
|
|
114
|
+
"account": self.account,
|
|
115
|
+
"password": hashlib.md5(self.password.encode("utf-8")).hexdigest(), # 密码MD5加密
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
**kwargs,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# 调用父类的request方法发送请求
|
|
122
|
+
return self.request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
123
|
+
|
|
124
|
+
def login(
|
|
125
|
+
self,
|
|
126
|
+
client: Optional[httpx.Client] = None,
|
|
127
|
+
client_kwargs: Optional[dict] = None,
|
|
128
|
+
**kwargs
|
|
129
|
+
) -> httpx.Response:
|
|
130
|
+
"""
|
|
131
|
+
用户登录SaaS系统
|
|
132
|
+
|
|
133
|
+
该方法向登录接口发送POST请求,携带账号和MD5加密后的密码进行身份认证。
|
|
134
|
+
登录成功后,从响应头中提取authorization token并存储在实例中。
|
|
135
|
+
|
|
136
|
+
:param client: 可选的同步httpx.Client实例,若未提供则使用默认客户端
|
|
137
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
138
|
+
:param kwargs: 额外的请求参数,可覆盖默认配置
|
|
139
|
+
:return: httpx.Response - HTTP响应对象
|
|
140
|
+
"""
|
|
141
|
+
# 处理参数默认值
|
|
142
|
+
client = client or None
|
|
143
|
+
client_kwargs = client_kwargs or {}
|
|
144
|
+
kwargs = kwargs or {}
|
|
145
|
+
|
|
146
|
+
# 构建登录请求参数
|
|
147
|
+
kwargs = {
|
|
148
|
+
**{
|
|
149
|
+
"method": "POST",
|
|
150
|
+
"url": "/api/user/loginInteractive", # 登录接口路径
|
|
151
|
+
"headers": {
|
|
152
|
+
"client": "co-pc"
|
|
153
|
+
},
|
|
154
|
+
"json": {
|
|
155
|
+
"account": self.account,
|
|
156
|
+
"password": hashlib.md5(self.password.encode("utf-8")).hexdigest(), # 密码MD5加密
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
**kwargs,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# 发送登录请求
|
|
163
|
+
response = self.request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
164
|
+
|
|
165
|
+
# 解析响应,提取token
|
|
166
|
+
if response.is_success:
|
|
167
|
+
# 使用JSON Schema校验响应数据结构是否符合预期
|
|
168
|
+
if json_is_valid(
|
|
169
|
+
schema={
|
|
170
|
+
"type": "object",
|
|
171
|
+
"properties": {
|
|
172
|
+
"userLoginV": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"userInfoV": {
|
|
176
|
+
"type": "object",
|
|
177
|
+
"properties": {
|
|
178
|
+
"id": {"type": "string", "minLength": 1}
|
|
179
|
+
},
|
|
180
|
+
"required": ["id"]
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
"required": ["userInfoV"]
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
"required": ["userLoginV"],
|
|
187
|
+
},
|
|
188
|
+
data=response.json(),
|
|
189
|
+
):
|
|
190
|
+
# 从响应头中获取authorization token
|
|
191
|
+
self.token = response.headers.get("authorization", "")
|
|
192
|
+
else:
|
|
193
|
+
# 响应数据结构不符合预期,清空token
|
|
194
|
+
self.token = ""
|
|
195
|
+
|
|
196
|
+
return response
|
|
197
|
+
|
|
198
|
+
def refresh_token(
|
|
199
|
+
self,
|
|
200
|
+
login_func_kwargs: Optional[dict] = None,
|
|
201
|
+
query_space_manage_tree_func_kwargs: Optional[dict] = None,
|
|
202
|
+
) -> "Saas":
|
|
203
|
+
"""
|
|
204
|
+
刷新并获取有效的认证Token
|
|
205
|
+
|
|
206
|
+
该方法实现了智能Token刷新逻辑:
|
|
207
|
+
1. 如果未配置缓存实例,直接调用login获取新token
|
|
208
|
+
2. 如果配置了缓存,优先从缓存中读取token
|
|
209
|
+
3. 缓存中没有token或token无效时,调用login重新获取
|
|
210
|
+
4. 通过query_space_manage_tree验证token是否有效
|
|
211
|
+
5. 验证通过后将token存入缓存
|
|
212
|
+
|
|
213
|
+
:param login_func_kwargs: 传递给login方法的额外参数
|
|
214
|
+
:param query_space_manage_tree_func_kwargs: 传递给query_space_manage_tree方法的额外参数
|
|
215
|
+
:return: Saas实例本身,支持链式调用
|
|
216
|
+
"""
|
|
217
|
+
# 处理参数默认值
|
|
218
|
+
login_func_kwargs = login_func_kwargs or {}
|
|
219
|
+
query_space_manage_tree_func_kwargs = query_space_manage_tree_func_kwargs or {}
|
|
220
|
+
|
|
221
|
+
# 获取缓存配置
|
|
222
|
+
cache_key = self.cache_config.get("key", f"py_wisharetec_saas_{self.account}")
|
|
223
|
+
cache_expire = self.cache_config.get("expire", 7100)
|
|
224
|
+
cache_instance = self.cache_config.get("instance", None)
|
|
225
|
+
|
|
226
|
+
# 判断是否配置了有效的缓存实例
|
|
227
|
+
if not isinstance(cache_instance, Union[diskcache.Cache, redis.Redis]):
|
|
228
|
+
# 未配置缓存,直接登录获取token
|
|
229
|
+
self.login(**login_func_kwargs)
|
|
230
|
+
else:
|
|
231
|
+
# 从缓存中读取token
|
|
232
|
+
self.token = cache_instance.get(cache_key)
|
|
233
|
+
|
|
234
|
+
# 如果缓存中没有token或token无效,重新登录
|
|
235
|
+
if not isinstance(self.token, str) or not len(self.token):
|
|
236
|
+
self.login(**login_func_kwargs)
|
|
237
|
+
|
|
238
|
+
# 查询空间管理树验证token是否有效
|
|
239
|
+
response_json = self.query_space_manage_tree(**query_space_manage_tree_func_kwargs).json()
|
|
240
|
+
|
|
241
|
+
# 如果token无效,重新登录
|
|
242
|
+
if not json_is_valid(
|
|
243
|
+
schema={
|
|
244
|
+
"type": "array",
|
|
245
|
+
"minItems": 1,
|
|
246
|
+
},
|
|
247
|
+
data=response_json,
|
|
248
|
+
):
|
|
249
|
+
self.login(**login_func_kwargs)
|
|
250
|
+
|
|
251
|
+
# 如果token有效,将其存入缓存
|
|
252
|
+
if json_is_valid(
|
|
253
|
+
schema={
|
|
254
|
+
"type": "array",
|
|
255
|
+
"minItems": 1,
|
|
256
|
+
},
|
|
257
|
+
data=response_json,
|
|
258
|
+
):
|
|
259
|
+
if isinstance(cache_instance, diskcache.Cache):
|
|
260
|
+
cache_instance.set(cache_key, self.token, expire=cache_expire)
|
|
261
|
+
elif isinstance(cache_instance, redis.Redis):
|
|
262
|
+
cache_instance.set(cache_key, self.token, ex=cache_expire)
|
|
263
|
+
|
|
264
|
+
return self
|
|
265
|
+
|
|
266
|
+
def request_with_token(
|
|
267
|
+
self,
|
|
268
|
+
client: Optional[httpx.Client] = None,
|
|
269
|
+
client_kwargs: Optional[dict] = None,
|
|
270
|
+
**kwargs
|
|
271
|
+
) -> httpx.Response:
|
|
272
|
+
"""
|
|
273
|
+
发送请求并自动添加授权头
|
|
274
|
+
|
|
275
|
+
该方法会在请求头中自动添加client标识和authorization token,
|
|
276
|
+
确保请求携带有效的身份认证信息。
|
|
277
|
+
|
|
278
|
+
:param client: 可选的同步httpx.Client实例,若未提供则使用默认客户端
|
|
279
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
280
|
+
:param kwargs: 传递给request的额外参数,可覆盖headers配置
|
|
281
|
+
:return: httpx.Response - HTTP响应对象
|
|
282
|
+
"""
|
|
283
|
+
# 处理参数默认值
|
|
284
|
+
client = client or None
|
|
285
|
+
client_kwargs = client_kwargs or {}
|
|
286
|
+
kwargs = kwargs or {}
|
|
287
|
+
|
|
288
|
+
# 构建请求头,添加认证信息
|
|
289
|
+
kwargs = {
|
|
290
|
+
"headers": {
|
|
291
|
+
"client": "co-pc", # 客户端标识
|
|
292
|
+
"authorization": self.token, # 认证token
|
|
293
|
+
},
|
|
294
|
+
**kwargs,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
# 调用父类的request方法发送请求
|
|
298
|
+
return self.request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
299
|
+
|
|
300
|
+
async def async_query_space_manage_tree(
|
|
301
|
+
self,
|
|
302
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
303
|
+
client_kwargs: Optional[dict] = None,
|
|
304
|
+
**kwargs
|
|
305
|
+
) -> httpx.Response:
|
|
306
|
+
"""
|
|
307
|
+
异步查询空间管理树结构
|
|
308
|
+
|
|
309
|
+
该方法用于异步获取当前用户有权限访问的空间管理树数据,常用于验证Token是否有效。
|
|
310
|
+
请求会携带账号和MD5加密后的密码进行身份验证。
|
|
311
|
+
|
|
312
|
+
:param client: 可选的异步httpx.AsyncClient实例,若未提供则使用默认客户端
|
|
313
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
314
|
+
:param kwargs: 额外的请求参数,可覆盖默认配置
|
|
315
|
+
:return: httpx.Response - HTTP响应对象,成功时包含空间管理树数据
|
|
316
|
+
"""
|
|
317
|
+
# 处理参数默认值
|
|
318
|
+
client = client or None
|
|
319
|
+
client_kwargs = client_kwargs or {}
|
|
320
|
+
kwargs = kwargs or {}
|
|
321
|
+
|
|
322
|
+
# 构建请求参数,合并默认配置与用户传入的配置
|
|
323
|
+
kwargs = {
|
|
324
|
+
**{
|
|
325
|
+
"method": "GET",
|
|
326
|
+
"url": "/api/space/space/manageTree",
|
|
327
|
+
"headers": {
|
|
328
|
+
"client": "co-pc",
|
|
329
|
+
"authorization": self.token,
|
|
330
|
+
},
|
|
331
|
+
"json": {
|
|
332
|
+
"account": self.account,
|
|
333
|
+
"password": hashlib.md5(self.password.encode("utf-8")).hexdigest(), # 密码MD5加密
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
**kwargs,
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
# 调用异步请求方法,自动添加授权头
|
|
340
|
+
return await self.async_request_with_token(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
341
|
+
|
|
342
|
+
async def async_login(
|
|
343
|
+
self,
|
|
344
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
345
|
+
client_kwargs: Optional[dict] = None,
|
|
346
|
+
**kwargs
|
|
347
|
+
) -> httpx.Response:
|
|
348
|
+
"""
|
|
349
|
+
异步用户登录SaaS系统
|
|
350
|
+
|
|
351
|
+
该方法向登录接口异步发送POST请求,携带账号和MD5加密后的密码进行身份认证。
|
|
352
|
+
登录成功后,从响应头中提取authorization token并存储在实例中。
|
|
353
|
+
|
|
354
|
+
:param client: 可选的异步httpx.AsyncClient实例,若未提供则使用默认客户端
|
|
355
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
356
|
+
:param kwargs: 额外的请求参数,可覆盖默认配置
|
|
357
|
+
:return: httpx.Response - HTTP响应对象
|
|
358
|
+
"""
|
|
359
|
+
# 处理参数默认值
|
|
360
|
+
client = client or None
|
|
361
|
+
client_kwargs = client_kwargs or {}
|
|
362
|
+
kwargs = kwargs or {}
|
|
363
|
+
|
|
364
|
+
# 构建登录请求参数
|
|
365
|
+
kwargs = {
|
|
366
|
+
**{
|
|
367
|
+
"method": "POST",
|
|
368
|
+
"url": "/api/user/loginInteractive", # 登录接口路径
|
|
369
|
+
"headers": {
|
|
370
|
+
"client": "co-pc"
|
|
371
|
+
},
|
|
372
|
+
"json": {
|
|
373
|
+
"account": self.account,
|
|
374
|
+
"password": hashlib.md5(self.password.encode("utf-8")).hexdigest(), # 密码MD5加密
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
**kwargs,
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
# 发送异步登录请求
|
|
381
|
+
response = await self.async_request_with_token(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
382
|
+
|
|
383
|
+
# 解析响应,提取token
|
|
384
|
+
if response.is_success:
|
|
385
|
+
# 使用JSON Schema校验响应数据结构是否符合预期
|
|
386
|
+
if json_is_valid(
|
|
387
|
+
schema={
|
|
388
|
+
"type": "object",
|
|
389
|
+
"properties": {
|
|
390
|
+
"userLoginV": {
|
|
391
|
+
"type": "object",
|
|
392
|
+
"properties": {
|
|
393
|
+
"userInfoV": {
|
|
394
|
+
"type": "object",
|
|
395
|
+
"properties": {
|
|
396
|
+
"id": {"type": "string", "minLength": 1}
|
|
397
|
+
},
|
|
398
|
+
"required": ["id"]
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
"required": ["userInfoV"]
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
"required": ["userLoginV"],
|
|
405
|
+
},
|
|
406
|
+
data=response.json(),
|
|
407
|
+
):
|
|
408
|
+
# 从响应头中获取authorization token
|
|
409
|
+
self.token = response.headers.get("authorization", "")
|
|
410
|
+
else:
|
|
411
|
+
# 响应数据结构不符合预期,清空token
|
|
412
|
+
self.token = ""
|
|
413
|
+
|
|
414
|
+
return response
|
|
415
|
+
|
|
416
|
+
async def async_refresh_token(
|
|
417
|
+
self,
|
|
418
|
+
login_func_kwargs: Optional[dict] = None,
|
|
419
|
+
query_space_manage_tree_func_kwargs: Optional[dict] = None,
|
|
420
|
+
) -> "Saas":
|
|
421
|
+
"""
|
|
422
|
+
异步刷新并获取有效的认证Token
|
|
423
|
+
|
|
424
|
+
该方法实现了异步的智能Token刷新逻辑:
|
|
425
|
+
1. 如果未配置缓存实例,直接调用async_login获取新token
|
|
426
|
+
2. 如果配置了缓存,优先从缓存中读取token
|
|
427
|
+
3. 缓存中没有token或token无效时,调用async_login重新获取
|
|
428
|
+
4. 通过async_query_space_manage_tree验证token是否有效
|
|
429
|
+
5. 验证通过后将token存入缓存
|
|
430
|
+
|
|
431
|
+
:param login_func_kwargs: 传递给async_login方法的额外参数
|
|
432
|
+
:param query_space_manage_tree_func_kwargs: 传递给async_query_space_manage_tree方法的额外参数
|
|
433
|
+
:return: Saas实例本身,支持链式调用
|
|
434
|
+
"""
|
|
435
|
+
# 处理参数默认值
|
|
436
|
+
login_func_kwargs = login_func_kwargs or {}
|
|
437
|
+
query_space_manage_tree_func_kwargs = query_space_manage_tree_func_kwargs or {}
|
|
438
|
+
|
|
439
|
+
# 获取缓存配置
|
|
440
|
+
cache_key = self.cache_config.get("key", f"py_wisharetec_saas_{self.account}")
|
|
441
|
+
cache_expire = self.cache_config.get("expire", 7100)
|
|
442
|
+
cache_instance = self.cache_config.get("instance", None)
|
|
443
|
+
|
|
444
|
+
# 判断是否配置了有效的缓存实例
|
|
445
|
+
if not isinstance(cache_instance, Union[diskcache.Cache, redis.Redis]):
|
|
446
|
+
# 未配置缓存,直接异步登录获取token
|
|
447
|
+
await self.async_login(**login_func_kwargs)
|
|
448
|
+
else:
|
|
449
|
+
# 从缓存中读取token
|
|
450
|
+
self.token = cache_instance.get(cache_key)
|
|
451
|
+
|
|
452
|
+
# 如果缓存中没有token或token无效,重新异步登录
|
|
453
|
+
if not isinstance(self.token, str) or not len(self.token):
|
|
454
|
+
await self.async_login(**login_func_kwargs)
|
|
455
|
+
|
|
456
|
+
# 异步查询空间管理树验证token是否有效
|
|
457
|
+
response = await self.async_query_space_manage_tree(**query_space_manage_tree_func_kwargs)
|
|
458
|
+
|
|
459
|
+
# 如果token无效,重新异步登录
|
|
460
|
+
if not json_is_valid(
|
|
461
|
+
schema={
|
|
462
|
+
"type": "object",
|
|
463
|
+
"properties": {
|
|
464
|
+
"children": {"type": "array", "minItems": 1},
|
|
465
|
+
},
|
|
466
|
+
"required": ["children"],
|
|
467
|
+
},
|
|
468
|
+
data=response.json(),
|
|
469
|
+
):
|
|
470
|
+
await self.async_login(**login_func_kwargs)
|
|
471
|
+
|
|
472
|
+
# 再次验证token并保存到缓存
|
|
473
|
+
response = await self.async_query_space_manage_tree(**query_space_manage_tree_func_kwargs)
|
|
474
|
+
if json_is_valid(
|
|
475
|
+
schema={
|
|
476
|
+
"type": "object",
|
|
477
|
+
"properties": {
|
|
478
|
+
"children": {"type": "array", "minItems": 1},
|
|
479
|
+
},
|
|
480
|
+
"required": ["children"],
|
|
481
|
+
},
|
|
482
|
+
data=response.json(),
|
|
483
|
+
):
|
|
484
|
+
if isinstance(cache_instance, diskcache.Cache):
|
|
485
|
+
cache_instance.set(cache_key, self.token, expire=cache_expire)
|
|
486
|
+
elif isinstance(cache_instance, redis.Redis):
|
|
487
|
+
cache_instance.set(cache_key, self.token, ex=cache_expire)
|
|
488
|
+
|
|
489
|
+
return self
|
|
490
|
+
|
|
491
|
+
async def async_request_with_token(
|
|
492
|
+
self,
|
|
493
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
494
|
+
client_kwargs: Optional[dict] = None,
|
|
495
|
+
**kwargs
|
|
496
|
+
) -> httpx.Response:
|
|
497
|
+
"""
|
|
498
|
+
异步发送请求并自动添加授权头
|
|
499
|
+
|
|
500
|
+
该方法会在请求头中自动添加client标识和authorization token,
|
|
501
|
+
确保请求携带有效的身份认证信息。
|
|
502
|
+
|
|
503
|
+
:param client: 可选的异步httpx.AsyncClient实例,若未提供则使用默认客户端
|
|
504
|
+
:param client_kwargs: 可选的客户端配置参数
|
|
505
|
+
:param kwargs: 传递给async_request的额外参数,可覆盖headers配置
|
|
506
|
+
:return: httpx.Response - HTTP响应对象
|
|
507
|
+
"""
|
|
508
|
+
# 处理参数默认值
|
|
509
|
+
client = client or None
|
|
510
|
+
client_kwargs = client_kwargs or {}
|
|
511
|
+
kwargs = kwargs or {}
|
|
512
|
+
|
|
513
|
+
# 构建请求头,添加认证信息
|
|
514
|
+
kwargs = {
|
|
515
|
+
"headers": {
|
|
516
|
+
"client": "co-pc", # 客户端标识
|
|
517
|
+
"authorization": self.token, # 认证token
|
|
518
|
+
},
|
|
519
|
+
**kwargs,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
# 调用父类的async_request方法发送异步请求
|
|
523
|
+
return await self.async_request(client=client, client_kwargs=client_kwargs, **kwargs)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Optional, Union
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from jsonpath_ng import parse
|
|
8
|
+
from jsonschema.validators import Draft202012Validator
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def json_find_first(expression: str, data: Any) -> Any:
|
|
12
|
+
"""
|
|
13
|
+
使用 JSONPath 表达式从 JSON 数据中查找第一个匹配项
|
|
14
|
+
|
|
15
|
+
该函数使用 jsonpath-ng 库解析 JSONPath 表达式,并从给定的数据中提取第一个匹配的值。
|
|
16
|
+
常用于从复杂的 JSON 结构中快速定位特定字段。
|
|
17
|
+
|
|
18
|
+
:param expression: JSONPath 表达式,用于指定要查找的数据路径
|
|
19
|
+
例如: "$.userInfoV.id" 表示查找根节点下的 userInfoV 对象的 id 字段
|
|
20
|
+
:param data: 待查询的 JSON 数据(字典或列表)
|
|
21
|
+
:return: Any - 第一个匹配的值,如果没有匹配项则返回 None
|
|
22
|
+
"""
|
|
23
|
+
# 使用 jsonpath-ng 解析表达式并查找所有匹配项
|
|
24
|
+
results = [i.value for i in parse(expression).find(data)]
|
|
25
|
+
|
|
26
|
+
# 检查结果列表是否非空,返回第一个匹配项
|
|
27
|
+
if isinstance(results, list) and len(results) > 0:
|
|
28
|
+
return results[0]
|
|
29
|
+
|
|
30
|
+
# 没有找到匹配项,返回 None
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def json_is_valid(schema: Optional[dict], data: Any) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
校验 JSON 数据是否符合指定的 JSON Schema
|
|
37
|
+
|
|
38
|
+
该函数使用 JSON Schema Draft 2020-12 规范对数据进行校验,
|
|
39
|
+
确保数据结构符合预期。常用于 API 响应数据的格式验证。
|
|
40
|
+
|
|
41
|
+
:param schema: JSON Schema 字典,定义了数据应满足的结构约束
|
|
42
|
+
例如: {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]}
|
|
43
|
+
:param data: 待校验的 JSON 数据(可以是任意类型)
|
|
44
|
+
:return: bool - 校验是否成功,符合 Schema 返回 True,否则返回 False
|
|
45
|
+
"""
|
|
46
|
+
# 使用 Draft202012Validator 进行 Schema 校验
|
|
47
|
+
return Draft202012Validator(schema).is_valid(data)
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-wisharetec-toolkit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
5
|
+
Author-email: Guolei <174000902@qq.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 郭磊
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://gitee.com/guolei19850528/py_wisharetec_toolkit
|
|
29
|
+
Project-URL: Repository, https://gitee.com/guolei19850528/py_wisharetec_toolkit.git
|
|
30
|
+
Keywords: wisharetec,python,client,api,wisharetec saas,saas
|
|
31
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: Programming Language :: Python :: 3
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
39
|
+
Classifier: Operating System :: OS Independent
|
|
40
|
+
Requires-Python: >=3.10
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: httpx>=0.27.0
|
|
44
|
+
Requires-Dist: pydantic>=2.0
|
|
45
|
+
Requires-Dist: jsonpath-ng>=1.5.3
|
|
46
|
+
Requires-Dist: jsonschema>=4.21.0
|
|
47
|
+
Requires-Dist: diskcache>=5.6.3
|
|
48
|
+
Requires-Dist: redis>=4.6.0
|
|
49
|
+
Requires-Dist: py-httpx-toolkit>=1.0.0
|
|
50
|
+
Provides-Extra: dev
|
|
51
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
52
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
53
|
+
Requires-Dist: setuptools>=61.0; extra == "dev"
|
|
54
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
55
|
+
Dynamic: license-file
|
|
56
|
+
|
|
57
|
+
# py-wisharetec-toolkit
|
|
58
|
+
|
|
59
|
+
Wisharetec SaaS 系统的 Python 客户端库,提供同步和异步接口调用能力。
|
|
60
|
+
|
|
61
|
+
## 功能特性
|
|
62
|
+
|
|
63
|
+
- 封装 SaaS 系统的认证机制,包括登录、令牌刷新和令牌缓存
|
|
64
|
+
- 提供同步和异步两种 HTTP 请求模式
|
|
65
|
+
- 集成 JSON Schema 校验和 JSONPath 查询工具
|
|
66
|
+
- 支持 diskcache 和 Redis 两种缓存后端
|
|
67
|
+
|
|
68
|
+
## 安装
|
|
69
|
+
|
|
70
|
+
### 使用 uv(推荐)
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
uv add py-wisharetec-toolkit
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 使用 pip
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install py-wisharetec-toolkit
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 依赖包
|
|
83
|
+
|
|
84
|
+
| 依赖包 | 版本要求 | 说明 |
|
|
85
|
+
|--------|----------|------|
|
|
86
|
+
| httpx | >=0.27.0 | HTTP 客户端库,支持同步和异步请求 |
|
|
87
|
+
| pydantic | >=2.0 | 数据验证和序列化 |
|
|
88
|
+
| jsonpath-ng | >=1.5.3 | JSONPath 查询工具 |
|
|
89
|
+
| jsonschema | >=4.21.0 | JSON Schema 校验 |
|
|
90
|
+
| diskcache | >=5.6.3 | 本地磁盘缓存 |
|
|
91
|
+
| redis | >=4.6.0 | Redis 缓存支持 |
|
|
92
|
+
| py-httpx-toolkit | >=1.0.0 | HTTP 请求工具封装 |
|
|
93
|
+
|
|
94
|
+
## API 说明
|
|
95
|
+
|
|
96
|
+
### Saas 类
|
|
97
|
+
|
|
98
|
+
#### 初始化
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
102
|
+
|
|
103
|
+
saas = Saas(
|
|
104
|
+
base_url="https://saas.wisharetec.com/",
|
|
105
|
+
account="your_account",
|
|
106
|
+
password="your_password",
|
|
107
|
+
cache_config={
|
|
108
|
+
"instance": None, # diskcache.Cache 或 redis.Redis 实例
|
|
109
|
+
"key": "py_wisharetec_saas_account",
|
|
110
|
+
"expire": 7100,
|
|
111
|
+
},
|
|
112
|
+
client_kwargs={
|
|
113
|
+
"timeout": 60,
|
|
114
|
+
"verify": False,
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### 同步方法
|
|
120
|
+
|
|
121
|
+
| 方法 | 说明 |
|
|
122
|
+
|------|------|
|
|
123
|
+
| `login()` | 用户登录,获取认证 Token |
|
|
124
|
+
| `refresh_token()` | 智能刷新 Token(支持缓存) |
|
|
125
|
+
| `query_space_manage_tree()` | 查询空间管理树结构 |
|
|
126
|
+
| `request_with_token()` | 发送带授权头的请求 |
|
|
127
|
+
|
|
128
|
+
#### 异步方法
|
|
129
|
+
|
|
130
|
+
| 方法 | 说明 |
|
|
131
|
+
|------|------|
|
|
132
|
+
| `async_login()` | 异步用户登录 |
|
|
133
|
+
| `async_refresh_token()` | 异步智能刷新 Token |
|
|
134
|
+
| `async_query_space_manage_tree()` | 异步查询空间管理树 |
|
|
135
|
+
| `async_request_with_token()` | 异步发送带授权头的请求 |
|
|
136
|
+
|
|
137
|
+
## utils 工具函数
|
|
138
|
+
|
|
139
|
+
### json_find_first
|
|
140
|
+
|
|
141
|
+
使用 JSONPath 表达式从 JSON 数据中查找第一个匹配项。
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from py_wisharetec_toolkit.saas.utils import json_find_first
|
|
145
|
+
|
|
146
|
+
data = {"userInfoV": {"id": "123"}}
|
|
147
|
+
result = json_find_first("$.userInfoV.id", data)
|
|
148
|
+
# 返回: "123"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### json_is_valid
|
|
152
|
+
|
|
153
|
+
校验 JSON 数据是否符合指定的 JSON Schema。
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from py_wisharetec_toolkit.saas.utils import json_is_valid
|
|
157
|
+
|
|
158
|
+
schema = {
|
|
159
|
+
"type": "object",
|
|
160
|
+
"properties": {"id": {"type": "string"}},
|
|
161
|
+
"required": ["id"]
|
|
162
|
+
}
|
|
163
|
+
data = {"id": "123"}
|
|
164
|
+
result = json_is_valid(schema, data)
|
|
165
|
+
# 返回: True
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 使用示例
|
|
169
|
+
|
|
170
|
+
### 同步方式
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
174
|
+
|
|
175
|
+
saas = Saas(
|
|
176
|
+
account="test",
|
|
177
|
+
password="password"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# 登录获取 Token
|
|
181
|
+
saas.login()
|
|
182
|
+
|
|
183
|
+
# 发送请求
|
|
184
|
+
response = saas.request_with_token(
|
|
185
|
+
method="GET",
|
|
186
|
+
url="/api/data/list"
|
|
187
|
+
)
|
|
188
|
+
print(response.json())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 异步方式
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import asyncio
|
|
195
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
196
|
+
|
|
197
|
+
async def main():
|
|
198
|
+
saas = Saas(
|
|
199
|
+
account="test",
|
|
200
|
+
password="password"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# 异步登录
|
|
204
|
+
await saas.async_login()
|
|
205
|
+
|
|
206
|
+
# 异步发送请求
|
|
207
|
+
response = await saas.async_request_with_token(
|
|
208
|
+
method="GET",
|
|
209
|
+
url="/api/data/list"
|
|
210
|
+
)
|
|
211
|
+
print(response.json())
|
|
212
|
+
|
|
213
|
+
asyncio.run(main())
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 使用缓存
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import diskcache
|
|
220
|
+
from py_wisharetec_toolkit.saas import Saas
|
|
221
|
+
|
|
222
|
+
cache = diskcache.Cache("./cache")
|
|
223
|
+
|
|
224
|
+
saas = Saas(
|
|
225
|
+
account="test",
|
|
226
|
+
password="password",
|
|
227
|
+
cache_config={
|
|
228
|
+
"instance": cache,
|
|
229
|
+
"key": "saas_token",
|
|
230
|
+
"expire": 7100
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# 自动从缓存获取或刷新 Token
|
|
235
|
+
saas.refresh_token()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 作者
|
|
239
|
+
|
|
240
|
+
**Guolei**
|
|
241
|
+
|
|
242
|
+
- 邮箱: 174000902@qq.com
|
|
243
|
+
|
|
244
|
+
## 项目主页
|
|
245
|
+
|
|
246
|
+
- **Gitee**: [https://gitee.com/guolei19850528/py_wisharetec_toolkit](https://gitee.com/guolei19850528/py_wisharetec_toolkit)
|
|
247
|
+
|
|
248
|
+
## 许可证
|
|
249
|
+
|
|
250
|
+
MIT License
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/py_wisharetec_toolkit/__init__.py
|
|
5
|
+
src/py_wisharetec_toolkit.egg-info/PKG-INFO
|
|
6
|
+
src/py_wisharetec_toolkit.egg-info/SOURCES.txt
|
|
7
|
+
src/py_wisharetec_toolkit.egg-info/dependency_links.txt
|
|
8
|
+
src/py_wisharetec_toolkit.egg-info/requires.txt
|
|
9
|
+
src/py_wisharetec_toolkit.egg-info/top_level.txt
|
|
10
|
+
src/py_wisharetec_toolkit/saas/__init__.py
|
|
11
|
+
src/py_wisharetec_toolkit/saas/utils.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
py_wisharetec_toolkit
|