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.
Files changed (21) hide show
  1. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/PKG-INFO +12 -3
  2. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/pyproject.toml +1 -1
  3. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/__init__.py +0 -8
  4. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_clients.py +9 -9
  5. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_models.py +1 -9
  6. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_clients.py +32 -33
  7. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_clients.py +11 -11
  8. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/__init__.py +0 -6
  9. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/_clients.py +16 -19
  10. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/webhook/_models.py +1 -25
  11. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/.gitignore +0 -0
  12. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/LICENSE +0 -0
  13. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/README.md +0 -0
  14. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/__init__.py +0 -0
  15. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/py.typed +0 -0
  16. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/_caches.py +0 -0
  17. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/__init__.py +0 -0
  18. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/materials/_models.py +0 -0
  19. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/__init__.py +0 -0
  20. {py_wecom_toolkit-4.2.3 → py_wecom_toolkit-4.2.4}/src/py_wecom_toolkit/server/messages/_msgtypes.py +0 -0
  21. {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
1
+ Metadata-Version: 2.5
2
2
  Name: py-wecom-toolkit
3
- Version: 4.2.3
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: py-wecom-toolkit[dev,docs,test]; extra == 'all'
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.3"
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, ConfigError, NetworkError
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 ConfigError("corpid is required")
50
+ raise ValueError("corpid is required")
51
51
  if not corpsecret:
52
- raise ConfigError("corpsecret is required")
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
- NetworkError
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 NetworkError(f"gettoken request failed: {exc}") from exc
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 ConfigError("corpid is required")
155
+ raise ValueError("corpid is required")
156
156
  if not corpsecret:
157
- raise ConfigError("corpsecret is required")
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
- NetworkError
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 NetworkError(f"gettoken request failed: {exc}") from exc
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
- """企业应用服务异常及 API 响应模型。"""
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
 
@@ -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
- ConfigError
80
+ ValueError
82
81
  当 access_token 获取失败时抛出。
83
- NetworkError
82
+ ConnectionError
84
83
  当网络请求失败时抛出。
85
84
  """
86
85
  token_response = self.get_access_token()
87
86
  if not token_response.ok:
88
- raise ConfigError(
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 NetworkError(f"upload material request failed: {exc}") from exc
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 NetworkError(
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
- ConfigError
138
+ ValueError
140
139
  当 access_token 获取失败时抛出。
141
- NetworkError
140
+ ConnectionError
142
141
  当网络请求失败时抛出。
143
142
  """
144
143
  token_response = self.get_access_token()
145
144
  if not token_response.ok:
146
- raise ConfigError(
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 NetworkError(f"upload image request failed: {exc}") from exc
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 NetworkError(
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
- ConfigError
190
+ ValueError
192
191
  当 access_token 获取失败时抛出。
193
- NetworkError
192
+ ConnectionError
194
193
  当网络请求失败时抛出。
195
194
  """
196
195
  token_response = self.get_access_token()
197
196
  if not token_response.ok:
198
- raise ConfigError(
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 NetworkError(f"download material request failed: {exc}") from exc
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 NetworkError(
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
- ConfigError
284
+ ValueError
286
285
  当 access_token 获取失败时抛出。
287
- NetworkError
286
+ ConnectionError
288
287
  当网络请求失败时抛出。
289
288
  """
290
289
  token_response = await self.get_access_token()
291
290
  if not token_response.ok:
292
- raise ConfigError(
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 NetworkError(f"upload material request failed: {exc}") from exc
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 NetworkError(
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
- ConfigError
340
+ ValueError
342
341
  当 access_token 获取失败时抛出。
343
- NetworkError
342
+ ConnectionError
344
343
  当网络请求失败时抛出。
345
344
  """
346
345
  token_response = await self.get_access_token()
347
346
  if not token_response.ok:
348
- raise ConfigError(
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 NetworkError(f"upload image request failed: {exc}") from exc
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 NetworkError(
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
- ConfigError
392
+ ValueError
394
393
  当 access_token 获取失败时抛出。
395
- NetworkError
394
+ ConnectionError
396
395
  当网络请求失败时抛出。
397
396
  """
398
397
  token_response = await self.get_access_token()
399
398
  if not token_response.ok:
400
- raise ConfigError(
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 NetworkError(f"download material request failed: {exc}") from exc
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 NetworkError(
416
+ raise ConnectionError(
418
417
  f"download material failed: errcode={data.get('errcode')}, "
419
418
  f"errmsg={data.get('errmsg')}"
420
419
  )
@@ -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, ConfigError, NetworkError
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
- ConfigError
76
+ ValueError
77
77
  当 access_token 获取失败或 agentid 为空时抛出。
78
- NetworkError
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 ConfigError("agentid is required")
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 ConfigError(
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 NetworkError(f"send message request failed: {exc}") from exc
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
- ConfigError
162
+ ValueError
163
163
  当 access_token 获取失败或 agentid 为空时抛出。
164
- NetworkError
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 ConfigError("agentid is required")
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 ConfigError(
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 NetworkError(f"send message request failed: {exc}") from exc
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
- ConfigError
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 ConfigError("webhook key is required")
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
- NetworkError
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 NetworkError(f"request failed: {exc}") from exc
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
- UploadError
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 NetworkError(f"upload request failed: {exc}") from exc
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 UploadError(
360
- data.get("errmsg", "upload failed"), errcode=data.get("errcode", -1)
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 ConfigError("webhook key is required")
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
- NetworkError
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 NetworkError(f"request failed: {exc}") from exc
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 NetworkError(f"upload request failed: {exc}") from exc
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 UploadError(
672
- data.get("errmsg", "upload failed"), errcode=data.get("errcode", -1)
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 异常及 API 响应模型。"""
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