py-wecom-toolkit 4.2.3__tar.gz → 4.2.4__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_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/PKG-INFO +12 -3
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/pyproject.toml +1 -1
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/__init__.py +0 -8
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_clients.py +9 -9
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_models.py +1 -9
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_clients.py +32 -33
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_clients.py +11 -11
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/__init__.py +0 -6
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/_clients.py +16 -19
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/_models.py +1 -25
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/.gitignore +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/LICENSE +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/README.md +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/__init__.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/py.typed +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_caches.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/__init__.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_models.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/__init__.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_msgtypes.py +0 -0
- {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/_msgtypes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: py-wecom-toolkit
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.4
|
|
4
4
|
Summary: 企业微信 API 工具包
|
|
5
5
|
Project-URL: HomePage, https://gitee.com/guolei19850528/py_wecom_toolkit
|
|
6
6
|
Project-URL: Repository, https://gitee.com/guolei19850528/py_wecom_toolkit
|
|
@@ -28,7 +28,16 @@ Requires-Dist: httpx<1.0,>=0.24.0
|
|
|
28
28
|
Requires-Dist: pydantic<3.0,>=2.0.0
|
|
29
29
|
Requires-Dist: redis>=5.0.0
|
|
30
30
|
Provides-Extra: all
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: mkdocs-material>=9.0.0; extra == 'all'
|
|
32
|
+
Requires-Dist: mkdocs>=1.5.0; extra == 'all'
|
|
33
|
+
Requires-Dist: mypy>=1.10.0; extra == 'all'
|
|
34
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'all'
|
|
35
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
|
|
36
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest>=8.0.0; extra == 'all'
|
|
38
|
+
Requires-Dist: python-dotenv>=1.0.0; extra == 'all'
|
|
39
|
+
Requires-Dist: respx>=0.21.0; extra == 'all'
|
|
40
|
+
Requires-Dist: ruff>=0.8.0; extra == 'all'
|
|
32
41
|
Provides-Extra: dev
|
|
33
42
|
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
34
43
|
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
# 项目发布元数据及支持的最低 Python 版本。
|
|
7
7
|
[project]
|
|
8
8
|
name = "py-wecom-toolkit"
|
|
9
|
-
version = "4.2.
|
|
9
|
+
version = "4.2.4"
|
|
10
10
|
description = "企业微信 API 工具包"
|
|
11
11
|
# 发布后不会自动更新 README.md 内容,需手动维护。
|
|
12
12
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
@@ -30,12 +30,6 @@ from ._models import (
|
|
|
30
30
|
from ._models import (
|
|
31
31
|
BaseResponse as ServerBaseResponse,
|
|
32
32
|
)
|
|
33
|
-
from ._models import (
|
|
34
|
-
ConfigError as ServerConfigError,
|
|
35
|
-
)
|
|
36
|
-
from ._models import (
|
|
37
|
-
NetworkError as ServerNetworkError,
|
|
38
|
-
)
|
|
39
33
|
|
|
40
34
|
__all__ = [
|
|
41
35
|
"ServerAccessTokenCache",
|
|
@@ -47,7 +41,5 @@ __all__ = [
|
|
|
47
41
|
"ServerAsyncClient",
|
|
48
42
|
"ServerBaseResponse",
|
|
49
43
|
"ServerClient",
|
|
50
|
-
"ServerConfigError",
|
|
51
|
-
"ServerNetworkError",
|
|
52
44
|
"server_build_access_token_cache_key",
|
|
53
45
|
]
|
|
@@ -13,7 +13,7 @@ from ._caches import (
|
|
|
13
13
|
AccessTokenDiskcacheCache,
|
|
14
14
|
build_access_token_cache_key,
|
|
15
15
|
)
|
|
16
|
-
from ._models import AccessTokenResponse
|
|
16
|
+
from ._models import AccessTokenResponse
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Client:
|
|
@@ -47,9 +47,9 @@ class Client:
|
|
|
47
47
|
) -> None:
|
|
48
48
|
"""使用企业凭证、缓存配置和 HTTP 参数初始化客户端。"""
|
|
49
49
|
if not corpid:
|
|
50
|
-
raise
|
|
50
|
+
raise ValueError("corpid is required")
|
|
51
51
|
if not corpsecret:
|
|
52
|
-
raise
|
|
52
|
+
raise ValueError("corpsecret is required")
|
|
53
53
|
self.corpid = corpid
|
|
54
54
|
self.corpsecret = corpsecret
|
|
55
55
|
self.cache = cache or AccessTokenDiskcacheCache()
|
|
@@ -95,7 +95,7 @@ class Client:
|
|
|
95
95
|
|
|
96
96
|
Raises
|
|
97
97
|
------
|
|
98
|
-
|
|
98
|
+
ConnectionError
|
|
99
99
|
当网络请求失败时抛出。
|
|
100
100
|
"""
|
|
101
101
|
cache_key = self._cache_key()
|
|
@@ -124,7 +124,7 @@ class Client:
|
|
|
124
124
|
try:
|
|
125
125
|
resp.raise_for_status()
|
|
126
126
|
except httpx.RequestError as exc:
|
|
127
|
-
raise
|
|
127
|
+
raise ConnectionError(f"gettoken request failed: {exc}") from exc
|
|
128
128
|
|
|
129
129
|
data = resp.json()
|
|
130
130
|
return AccessTokenResponse(**data)
|
|
@@ -152,9 +152,9 @@ class AsyncClient:
|
|
|
152
152
|
) -> None:
|
|
153
153
|
"""使用企业凭证、缓存配置和 HTTP 参数初始化异步客户端。"""
|
|
154
154
|
if not corpid:
|
|
155
|
-
raise
|
|
155
|
+
raise ValueError("corpid is required")
|
|
156
156
|
if not corpsecret:
|
|
157
|
-
raise
|
|
157
|
+
raise ValueError("corpsecret is required")
|
|
158
158
|
self.corpid = corpid
|
|
159
159
|
self.corpsecret = corpsecret
|
|
160
160
|
self.cache = cache or AccessTokenDiskcacheCache()
|
|
@@ -202,7 +202,7 @@ class AsyncClient:
|
|
|
202
202
|
|
|
203
203
|
Raises
|
|
204
204
|
------
|
|
205
|
-
|
|
205
|
+
ConnectionError
|
|
206
206
|
当网络请求失败时抛出。
|
|
207
207
|
"""
|
|
208
208
|
cache_key = self._cache_key()
|
|
@@ -231,7 +231,7 @@ class AsyncClient:
|
|
|
231
231
|
try:
|
|
232
232
|
resp.raise_for_status()
|
|
233
233
|
except httpx.RequestError as exc:
|
|
234
|
-
raise
|
|
234
|
+
raise ConnectionError(f"gettoken request failed: {exc}") from exc
|
|
235
235
|
|
|
236
236
|
data = resp.json()
|
|
237
237
|
return AccessTokenResponse(**data)
|
|
@@ -1,20 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: UTF-8 -*-
|
|
3
|
-
"""
|
|
3
|
+
"""企业应用服务 API 响应模型。"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, ConfigDict
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class ConfigError(Exception):
|
|
11
|
-
"""凭证或客户端配置无效时抛出。"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class NetworkError(Exception):
|
|
15
|
-
"""企业微信 API 网络请求失败时抛出。"""
|
|
16
|
-
|
|
17
|
-
|
|
18
10
|
class BaseResponse(BaseModel):
|
|
19
11
|
"""企业微信 API 响应的公共字段,并保留服务端附加字段。"""
|
|
20
12
|
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_clients.py
RENAMED
|
@@ -11,7 +11,6 @@ import httpx
|
|
|
11
11
|
|
|
12
12
|
from .._clients import AsyncClient as BaseAsyncClient
|
|
13
13
|
from .._clients import Client as BaseClient
|
|
14
|
-
from .._models import ConfigError, NetworkError
|
|
15
14
|
from ._models import MaterialTypeLiteral, UploadImgResponse, UploadResponse
|
|
16
15
|
|
|
17
16
|
|
|
@@ -78,14 +77,14 @@ class Client(BaseClient):
|
|
|
78
77
|
|
|
79
78
|
Raises
|
|
80
79
|
------
|
|
81
|
-
|
|
80
|
+
ValueError
|
|
82
81
|
当 access_token 获取失败时抛出。
|
|
83
|
-
|
|
82
|
+
ConnectionError
|
|
84
83
|
当网络请求失败时抛出。
|
|
85
84
|
"""
|
|
86
85
|
token_response = self.get_access_token()
|
|
87
86
|
if not token_response.ok:
|
|
88
|
-
raise
|
|
87
|
+
raise ValueError(
|
|
89
88
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
90
89
|
f"errmsg={token_response.errmsg}"
|
|
91
90
|
)
|
|
@@ -104,10 +103,10 @@ class Client(BaseClient):
|
|
|
104
103
|
resp = self._client.post(url, files=files)
|
|
105
104
|
resp.raise_for_status()
|
|
106
105
|
except httpx.RequestError as exc:
|
|
107
|
-
raise
|
|
106
|
+
raise ConnectionError(f"upload material request failed: {exc}") from exc
|
|
108
107
|
data = resp.json()
|
|
109
108
|
if data.get("errcode", 0) != 0:
|
|
110
|
-
raise
|
|
109
|
+
raise ConnectionError(
|
|
111
110
|
f"upload material failed: errcode={data.get('errcode')}, "
|
|
112
111
|
f"errmsg={data.get('errmsg')}"
|
|
113
112
|
)
|
|
@@ -136,14 +135,14 @@ class Client(BaseClient):
|
|
|
136
135
|
|
|
137
136
|
Raises
|
|
138
137
|
------
|
|
139
|
-
|
|
138
|
+
ValueError
|
|
140
139
|
当 access_token 获取失败时抛出。
|
|
141
|
-
|
|
140
|
+
ConnectionError
|
|
142
141
|
当网络请求失败时抛出。
|
|
143
142
|
"""
|
|
144
143
|
token_response = self.get_access_token()
|
|
145
144
|
if not token_response.ok:
|
|
146
|
-
raise
|
|
145
|
+
raise ValueError(
|
|
147
146
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
148
147
|
f"errmsg={token_response.errmsg}"
|
|
149
148
|
)
|
|
@@ -162,10 +161,10 @@ class Client(BaseClient):
|
|
|
162
161
|
resp = self._client.post(url, files=files)
|
|
163
162
|
resp.raise_for_status()
|
|
164
163
|
except httpx.RequestError as exc:
|
|
165
|
-
raise
|
|
164
|
+
raise ConnectionError(f"upload image request failed: {exc}") from exc
|
|
166
165
|
data = resp.json()
|
|
167
166
|
if data.get("errcode", 0) != 0:
|
|
168
|
-
raise
|
|
167
|
+
raise ConnectionError(
|
|
169
168
|
f"upload image failed: errcode={data.get('errcode')}, "
|
|
170
169
|
f"errmsg={data.get('errmsg')}"
|
|
171
170
|
)
|
|
@@ -179,7 +178,7 @@ class Client(BaseClient):
|
|
|
179
178
|
|
|
180
179
|
文档: https://developer.work.weixin.qq.com/document/path/90254
|
|
181
180
|
|
|
182
|
-
正确时返回媒体文件二进制流;错误时返回 JSON
|
|
181
|
+
正确时返回媒体文件二进制流;错误时返回 JSON 响应,会抛出内置异常。
|
|
183
182
|
|
|
184
183
|
Parameters
|
|
185
184
|
----------
|
|
@@ -188,14 +187,14 @@ class Client(BaseClient):
|
|
|
188
187
|
|
|
189
188
|
Raises
|
|
190
189
|
------
|
|
191
|
-
|
|
190
|
+
ValueError
|
|
192
191
|
当 access_token 获取失败时抛出。
|
|
193
|
-
|
|
192
|
+
ConnectionError
|
|
194
193
|
当网络请求失败时抛出。
|
|
195
194
|
"""
|
|
196
195
|
token_response = self.get_access_token()
|
|
197
196
|
if not token_response.ok:
|
|
198
|
-
raise
|
|
197
|
+
raise ValueError(
|
|
199
198
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
200
199
|
f"errmsg={token_response.errmsg}"
|
|
201
200
|
)
|
|
@@ -205,14 +204,14 @@ class Client(BaseClient):
|
|
|
205
204
|
resp = self._client.get(url)
|
|
206
205
|
resp.raise_for_status()
|
|
207
206
|
except httpx.RequestError as exc:
|
|
208
|
-
raise
|
|
207
|
+
raise ConnectionError(f"download material request failed: {exc}") from exc
|
|
209
208
|
|
|
210
209
|
# 企业微信下载接口:成功返回二进制流,错误返回 JSON
|
|
211
210
|
content_type = resp.headers.get("content-type", "")
|
|
212
211
|
if "application/json" in content_type:
|
|
213
212
|
data = resp.json()
|
|
214
213
|
if data.get("errcode", 0) != 0:
|
|
215
|
-
raise
|
|
214
|
+
raise ConnectionError(
|
|
216
215
|
f"download material failed: errcode={data.get('errcode')}, "
|
|
217
216
|
f"errmsg={data.get('errmsg')}"
|
|
218
217
|
)
|
|
@@ -282,14 +281,14 @@ class AsyncClient(BaseAsyncClient):
|
|
|
282
281
|
|
|
283
282
|
Raises
|
|
284
283
|
------
|
|
285
|
-
|
|
284
|
+
ValueError
|
|
286
285
|
当 access_token 获取失败时抛出。
|
|
287
|
-
|
|
286
|
+
ConnectionError
|
|
288
287
|
当网络请求失败时抛出。
|
|
289
288
|
"""
|
|
290
289
|
token_response = await self.get_access_token()
|
|
291
290
|
if not token_response.ok:
|
|
292
|
-
raise
|
|
291
|
+
raise ValueError(
|
|
293
292
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
294
293
|
f"errmsg={token_response.errmsg}"
|
|
295
294
|
)
|
|
@@ -308,10 +307,10 @@ class AsyncClient(BaseAsyncClient):
|
|
|
308
307
|
resp = await self._client.post(url, files=files)
|
|
309
308
|
resp.raise_for_status()
|
|
310
309
|
except httpx.RequestError as exc:
|
|
311
|
-
raise
|
|
310
|
+
raise ConnectionError(f"upload material request failed: {exc}") from exc
|
|
312
311
|
data = resp.json()
|
|
313
312
|
if data.get("errcode", 0) != 0:
|
|
314
|
-
raise
|
|
313
|
+
raise ConnectionError(
|
|
315
314
|
f"upload material failed: errcode={data.get('errcode')}, "
|
|
316
315
|
f"errmsg={data.get('errmsg')}"
|
|
317
316
|
)
|
|
@@ -338,14 +337,14 @@ class AsyncClient(BaseAsyncClient):
|
|
|
338
337
|
|
|
339
338
|
Raises
|
|
340
339
|
------
|
|
341
|
-
|
|
340
|
+
ValueError
|
|
342
341
|
当 access_token 获取失败时抛出。
|
|
343
|
-
|
|
342
|
+
ConnectionError
|
|
344
343
|
当网络请求失败时抛出。
|
|
345
344
|
"""
|
|
346
345
|
token_response = await self.get_access_token()
|
|
347
346
|
if not token_response.ok:
|
|
348
|
-
raise
|
|
347
|
+
raise ValueError(
|
|
349
348
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
350
349
|
f"errmsg={token_response.errmsg}"
|
|
351
350
|
)
|
|
@@ -364,10 +363,10 @@ class AsyncClient(BaseAsyncClient):
|
|
|
364
363
|
resp = await self._client.post(url, files=files)
|
|
365
364
|
resp.raise_for_status()
|
|
366
365
|
except httpx.RequestError as exc:
|
|
367
|
-
raise
|
|
366
|
+
raise ConnectionError(f"upload image request failed: {exc}") from exc
|
|
368
367
|
data = resp.json()
|
|
369
368
|
if data.get("errcode", 0) != 0:
|
|
370
|
-
raise
|
|
369
|
+
raise ConnectionError(
|
|
371
370
|
f"upload image failed: errcode={data.get('errcode')}, "
|
|
372
371
|
f"errmsg={data.get('errmsg')}"
|
|
373
372
|
)
|
|
@@ -381,7 +380,7 @@ class AsyncClient(BaseAsyncClient):
|
|
|
381
380
|
|
|
382
381
|
文档: https://developer.work.weixin.qq.com/document/path/90254
|
|
383
382
|
|
|
384
|
-
正确时返回媒体文件二进制流;错误时返回 JSON
|
|
383
|
+
正确时返回媒体文件二进制流;错误时返回 JSON 响应,会抛出内置异常。
|
|
385
384
|
|
|
386
385
|
Parameters
|
|
387
386
|
----------
|
|
@@ -390,14 +389,14 @@ class AsyncClient(BaseAsyncClient):
|
|
|
390
389
|
|
|
391
390
|
Raises
|
|
392
391
|
------
|
|
393
|
-
|
|
392
|
+
ValueError
|
|
394
393
|
当 access_token 获取失败时抛出。
|
|
395
|
-
|
|
394
|
+
ConnectionError
|
|
396
395
|
当网络请求失败时抛出。
|
|
397
396
|
"""
|
|
398
397
|
token_response = await self.get_access_token()
|
|
399
398
|
if not token_response.ok:
|
|
400
|
-
raise
|
|
399
|
+
raise ValueError(
|
|
401
400
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
402
401
|
f"errmsg={token_response.errmsg}"
|
|
403
402
|
)
|
|
@@ -407,14 +406,14 @@ class AsyncClient(BaseAsyncClient):
|
|
|
407
406
|
resp = await self._client.get(url)
|
|
408
407
|
resp.raise_for_status()
|
|
409
408
|
except httpx.RequestError as exc:
|
|
410
|
-
raise
|
|
409
|
+
raise ConnectionError(f"download material request failed: {exc}") from exc
|
|
411
410
|
|
|
412
411
|
# 企业微信下载接口:成功返回二进制流,错误返回 JSON
|
|
413
412
|
content_type = resp.headers.get("content-type", "")
|
|
414
413
|
if "application/json" in content_type:
|
|
415
414
|
data = resp.json()
|
|
416
415
|
if data.get("errcode", 0) != 0:
|
|
417
|
-
raise
|
|
416
|
+
raise ConnectionError(
|
|
418
417
|
f"download material failed: errcode={data.get('errcode')}, "
|
|
419
418
|
f"errmsg={data.get('errmsg')}"
|
|
420
419
|
)
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_clients.py
RENAMED
|
@@ -10,7 +10,7 @@ import httpx
|
|
|
10
10
|
|
|
11
11
|
from .._clients import AsyncClient as BaseAsyncClient
|
|
12
12
|
from .._clients import Client as BaseClient
|
|
13
|
-
from .._models import BaseResponse
|
|
13
|
+
from .._models import BaseResponse
|
|
14
14
|
from ._msgtypes import BaseMessage
|
|
15
15
|
|
|
16
16
|
|
|
@@ -73,18 +73,18 @@ class Client(BaseClient):
|
|
|
73
73
|
|
|
74
74
|
Raises
|
|
75
75
|
------
|
|
76
|
-
|
|
76
|
+
ValueError
|
|
77
77
|
当 access_token 获取失败或 agentid 为空时抛出。
|
|
78
|
-
|
|
78
|
+
ConnectionError
|
|
79
79
|
当网络请求失败时抛出。
|
|
80
80
|
"""
|
|
81
81
|
resolved_agentid = agentid if agentid is not None else self.agentid
|
|
82
82
|
if resolved_agentid is None:
|
|
83
|
-
raise
|
|
83
|
+
raise ValueError("agentid is required")
|
|
84
84
|
|
|
85
85
|
token_response = self.get_access_token()
|
|
86
86
|
if not token_response.ok:
|
|
87
|
-
raise
|
|
87
|
+
raise ValueError(
|
|
88
88
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
89
89
|
f"errmsg={token_response.errmsg}"
|
|
90
90
|
)
|
|
@@ -96,7 +96,7 @@ class Client(BaseClient):
|
|
|
96
96
|
resp = self._client.post(url, json=payload)
|
|
97
97
|
resp.raise_for_status()
|
|
98
98
|
except httpx.RequestError as exc:
|
|
99
|
-
raise
|
|
99
|
+
raise ConnectionError(f"send message request failed: {exc}") from exc
|
|
100
100
|
return BaseResponse(**resp.json())
|
|
101
101
|
|
|
102
102
|
|
|
@@ -159,18 +159,18 @@ class AsyncClient(BaseAsyncClient):
|
|
|
159
159
|
|
|
160
160
|
Raises
|
|
161
161
|
------
|
|
162
|
-
|
|
162
|
+
ValueError
|
|
163
163
|
当 access_token 获取失败或 agentid 为空时抛出。
|
|
164
|
-
|
|
164
|
+
ConnectionError
|
|
165
165
|
当网络请求失败时抛出。
|
|
166
166
|
"""
|
|
167
167
|
resolved_agentid = agentid if agentid is not None else self.agentid
|
|
168
168
|
if resolved_agentid is None:
|
|
169
|
-
raise
|
|
169
|
+
raise ValueError("agentid is required")
|
|
170
170
|
|
|
171
171
|
token_response = await self.get_access_token()
|
|
172
172
|
if not token_response.ok:
|
|
173
|
-
raise
|
|
173
|
+
raise ValueError(
|
|
174
174
|
f"获取 access_token 失败: errcode={token_response.errcode}, "
|
|
175
175
|
f"errmsg={token_response.errmsg}"
|
|
176
176
|
)
|
|
@@ -182,5 +182,5 @@ class AsyncClient(BaseAsyncClient):
|
|
|
182
182
|
resp = await self._client.post(url, json=payload)
|
|
183
183
|
resp.raise_for_status()
|
|
184
184
|
except httpx.RequestError as exc:
|
|
185
|
-
raise
|
|
185
|
+
raise ConnectionError(f"send message request failed: {exc}") from exc
|
|
186
186
|
return BaseResponse(**resp.json())
|
|
@@ -7,10 +7,7 @@ from __future__ import annotations
|
|
|
7
7
|
from ._clients import AsyncClient as WebhookAsyncClient
|
|
8
8
|
from ._clients import Client as WebhookClient
|
|
9
9
|
from ._models import BaseResponse as WebhookBaseResponse
|
|
10
|
-
from ._models import ConfigError as WebhookConfigError
|
|
11
|
-
from ._models import NetworkError as WebhookNetworkError
|
|
12
10
|
from ._models import Response as WebhookResponse
|
|
13
|
-
from ._models import UploadError as WebhookUploadError
|
|
14
11
|
from ._models import UploadMediaResponse as WebhookUploadMediaResponse
|
|
15
12
|
from ._msgtypes import Article as WebhookArticle
|
|
16
13
|
from ._msgtypes import CardAction as WebhookCardAction
|
|
@@ -44,7 +41,6 @@ __all__ = [
|
|
|
44
41
|
"WebhookBaseResponse",
|
|
45
42
|
"WebhookCardAction",
|
|
46
43
|
"WebhookClient",
|
|
47
|
-
"WebhookConfigError",
|
|
48
44
|
"WebhookEmphasisContent",
|
|
49
45
|
"WebhookFileContent",
|
|
50
46
|
"WebhookFileMsgType",
|
|
@@ -57,7 +53,6 @@ __all__ = [
|
|
|
57
53
|
"WebhookMarkdownMsgType",
|
|
58
54
|
"WebhookMarkdownV2Content",
|
|
59
55
|
"WebhookMarkdownV2MsgType",
|
|
60
|
-
"WebhookNetworkError",
|
|
61
56
|
"WebhookNewsContent",
|
|
62
57
|
"WebhookNewsMsgType",
|
|
63
58
|
"WebhookQuoteArea",
|
|
@@ -67,7 +62,6 @@ __all__ = [
|
|
|
67
62
|
"WebhookTemplateCardMsgType",
|
|
68
63
|
"WebhookTextContent",
|
|
69
64
|
"WebhookTextMsgType",
|
|
70
|
-
"WebhookUploadError",
|
|
71
65
|
"WebhookUploadMediaResponse",
|
|
72
66
|
"WebhookVoiceContent",
|
|
73
67
|
"WebhookVoiceMsgType",
|
|
@@ -11,10 +11,7 @@ import httpx
|
|
|
11
11
|
from pydantic import BaseModel
|
|
12
12
|
|
|
13
13
|
from ._models import (
|
|
14
|
-
ConfigError,
|
|
15
|
-
NetworkError,
|
|
16
14
|
Response,
|
|
17
|
-
UploadError,
|
|
18
15
|
UploadMediaResponse,
|
|
19
16
|
)
|
|
20
17
|
from ._msgtypes import (
|
|
@@ -61,7 +58,7 @@ class Client:
|
|
|
61
58
|
|
|
62
59
|
Raises
|
|
63
60
|
------
|
|
64
|
-
|
|
61
|
+
ValueError
|
|
65
62
|
当 ``key`` 为空时抛出。
|
|
66
63
|
"""
|
|
67
64
|
|
|
@@ -76,7 +73,7 @@ class Client:
|
|
|
76
73
|
httpx_client_kwargs: Optional[dict[str, Any]] = None,
|
|
77
74
|
) -> None:
|
|
78
75
|
if not key:
|
|
79
|
-
raise
|
|
76
|
+
raise ValueError("webhook key is required")
|
|
80
77
|
self.key = key
|
|
81
78
|
self.base_url = base_url.rstrip("/")
|
|
82
79
|
self.timeout = timeout
|
|
@@ -136,7 +133,7 @@ class Client:
|
|
|
136
133
|
|
|
137
134
|
Raises
|
|
138
135
|
------
|
|
139
|
-
|
|
136
|
+
ConnectionError
|
|
140
137
|
当网络请求失败时抛出。
|
|
141
138
|
"""
|
|
142
139
|
payload = message.model_dump(mode="json", exclude_none=True)
|
|
@@ -144,7 +141,7 @@ class Client:
|
|
|
144
141
|
resp = self._client.post(self._send_url(), json=payload)
|
|
145
142
|
resp.raise_for_status()
|
|
146
143
|
except httpx.RequestError as exc:
|
|
147
|
-
raise
|
|
144
|
+
raise ConnectionError(f"request failed: {exc}") from exc
|
|
148
145
|
return Response(**resp.json())
|
|
149
146
|
|
|
150
147
|
def send_text(
|
|
@@ -335,9 +332,7 @@ class Client:
|
|
|
335
332
|
|
|
336
333
|
Raises
|
|
337
334
|
------
|
|
338
|
-
|
|
339
|
-
当上传失败或 API 返回 errcode 不为 0 时抛出。
|
|
340
|
-
NetworkError
|
|
335
|
+
ConnectionError
|
|
341
336
|
当网络请求失败时抛出。
|
|
342
337
|
"""
|
|
343
338
|
if isinstance(file, (str, Path)):
|
|
@@ -353,11 +348,12 @@ class Client:
|
|
|
353
348
|
resp = self._client.post(self._upload_url(media_type), files=files)
|
|
354
349
|
resp.raise_for_status()
|
|
355
350
|
except httpx.RequestError as exc:
|
|
356
|
-
raise
|
|
351
|
+
raise ConnectionError(f"upload request failed: {exc}") from exc
|
|
357
352
|
data = resp.json()
|
|
358
353
|
if data.get("errcode", 0) != 0:
|
|
359
|
-
raise
|
|
360
|
-
data.get(
|
|
354
|
+
raise ConnectionError(
|
|
355
|
+
f"upload failed: {data.get('errmsg', 'upload failed')} "
|
|
356
|
+
f"(errcode: {data.get('errcode', -1)})"
|
|
361
357
|
)
|
|
362
358
|
return UploadMediaResponse(**data)
|
|
363
359
|
|
|
@@ -395,7 +391,7 @@ class AsyncClient:
|
|
|
395
391
|
httpx_client_kwargs: Optional[dict[str, Any]] = None,
|
|
396
392
|
) -> None:
|
|
397
393
|
if not key:
|
|
398
|
-
raise
|
|
394
|
+
raise ValueError("webhook key is required")
|
|
399
395
|
self.key = key
|
|
400
396
|
self.base_url = base_url.rstrip("/")
|
|
401
397
|
self.timeout = timeout
|
|
@@ -455,7 +451,7 @@ class AsyncClient:
|
|
|
455
451
|
|
|
456
452
|
Raises
|
|
457
453
|
------
|
|
458
|
-
|
|
454
|
+
ConnectionError
|
|
459
455
|
当网络请求失败时抛出。
|
|
460
456
|
"""
|
|
461
457
|
payload = message.model_dump(mode="json", exclude_none=True)
|
|
@@ -463,7 +459,7 @@ class AsyncClient:
|
|
|
463
459
|
resp = await self._client.post(self._send_url(), json=payload)
|
|
464
460
|
resp.raise_for_status()
|
|
465
461
|
except httpx.RequestError as exc:
|
|
466
|
-
raise
|
|
462
|
+
raise ConnectionError(f"request failed: {exc}") from exc
|
|
467
463
|
return Response(**resp.json())
|
|
468
464
|
|
|
469
465
|
async def send_text(
|
|
@@ -665,10 +661,11 @@ class AsyncClient:
|
|
|
665
661
|
resp = await self._client.post(self._upload_url(media_type), files=files)
|
|
666
662
|
resp.raise_for_status()
|
|
667
663
|
except httpx.RequestError as exc:
|
|
668
|
-
raise
|
|
664
|
+
raise ConnectionError(f"upload request failed: {exc}") from exc
|
|
669
665
|
data = resp.json()
|
|
670
666
|
if data.get("errcode", 0) != 0:
|
|
671
|
-
raise
|
|
672
|
-
data.get(
|
|
667
|
+
raise ConnectionError(
|
|
668
|
+
f"upload failed: {data.get('errmsg', 'upload failed')} "
|
|
669
|
+
f"(errcode: {data.get('errcode', -1)})"
|
|
673
670
|
)
|
|
674
671
|
return UploadMediaResponse(**data)
|
|
@@ -1,36 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: UTF-8 -*-
|
|
3
|
-
"""Webhook
|
|
3
|
+
"""Webhook API 响应模型。"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from typing import Optional
|
|
8
|
-
|
|
9
7
|
from pydantic import BaseModel, ConfigDict
|
|
10
8
|
|
|
11
9
|
|
|
12
|
-
class ConfigError(Exception):
|
|
13
|
-
"""Webhook 客户端配置无效时抛出。"""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class NetworkError(Exception):
|
|
17
|
-
"""Webhook API 网络请求失败时抛出。"""
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class UploadError(Exception):
|
|
21
|
-
"""文件上传失败时抛出,包含接口错误信息及可用的错误码。"""
|
|
22
|
-
|
|
23
|
-
def __init__(self, message: str, errcode: Optional[int] = None) -> None:
|
|
24
|
-
super().__init__(message)
|
|
25
|
-
self.message = message
|
|
26
|
-
self.errcode = errcode
|
|
27
|
-
|
|
28
|
-
def __str__(self) -> str:
|
|
29
|
-
if self.errcode is not None:
|
|
30
|
-
return f"[{self.errcode}] {self.message}"
|
|
31
|
-
return self.message
|
|
32
|
-
|
|
33
|
-
|
|
34
10
|
class BaseResponse(BaseModel):
|
|
35
11
|
"""企业微信 API 响应的公共字段;保留服务端返回的额外字段。"""
|
|
36
12
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/__init__.py
RENAMED
|
File without changes
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_models.py
RENAMED
|
File without changes
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/__init__.py
RENAMED
|
File without changes
|
{py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_msgtypes.py
RENAMED
|
File without changes
|
|
File without changes
|