py-wecom-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.
@@ -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,522 @@
1
+ Metadata-Version: 2.4
2
+ Name: py-wecom-toolkit
3
+ Version: 1.0.0
4
+ Summary: 企业微信 SDK,提供企业微信 API 的 Python 封装,支持同步和异步调用。
5
+ Author-email: Guolei <174000902@qq.com>
6
+ Maintainer-email: Guolei <174000902@qq.com>
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 郭磊
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Project-URL: Homepage, https://gitee.com/guolei19850528/py_wecom_toolkit
30
+ Project-URL: Repository, https://gitee.com/guolei19850528/py_wecom_toolkit.git
31
+ Project-URL: Documentation, https://gitee.com/guolei19850528/py_wecom_toolkit
32
+ Keywords: wecom,python,client,api,企业微信,异步调用,server,消息推送,文件上传,webhook,机器人
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Development Status :: 4 - Beta
35
+ Classifier: Intended Audience :: Developers
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Operating System :: OS Independent
42
+ Classifier: Topic :: Communications
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Typing :: Typed
45
+ Requires-Python: >=3.10
46
+ Description-Content-Type: text/markdown
47
+ License-File: LICENSE
48
+ Requires-Dist: httpx>=0.27.0
49
+ Requires-Dist: pydantic>=2.0
50
+ Requires-Dist: jsonpath-ng>=1.5.3
51
+ Requires-Dist: jsonschema>=4.21.0
52
+ Requires-Dist: diskcache>=5.6.3
53
+ Requires-Dist: redis>=4.6.0
54
+ Requires-Dist: py-httpx-toolkit>=1.0.1
55
+ Provides-Extra: dev
56
+ Requires-Dist: pytest>=7.0; extra == "dev"
57
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
58
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
59
+ Requires-Dist: setuptools>=61.0; extra == "dev"
60
+ Requires-Dist: twine>=4.0; extra == "dev"
61
+ Requires-Dist: build>=1.0; extra == "dev"
62
+ Dynamic: license-file
63
+
64
+ # py-wecom-toolkit
65
+
66
+ 企业微信 API 的 Python SDK,提供完整的企业微信接口封装,支持同步和异步调用方式。
67
+
68
+ ## 项目主页
69
+
70
+ - **项目地址**: [https://gitee.com/guolei19850528/py_wecom_toolkit](https://gitee.com/guolei19850528/py_wecom_toolkit)
71
+
72
+
73
+ ## 功能特性
74
+
75
+ ### Webhook 模块 (`py_wecom_toolkit.webhook`)
76
+ - 企业微信 Webhook 消息发送
77
+ - 支持文本、Markdown、MarkdownV2、图片、图文、文件、语音、模板卡片等消息类型
78
+ - 支持同步和异步两种调用方式
79
+ - 媒体文件上传功能
80
+
81
+ ### Server 模块 (`py_wecom_toolkit.server`)
82
+ - 企业微信 Server API 集成
83
+ - 获取 access_token(支持 diskcache 和 Redis 缓存)
84
+ - 获取 API 域名 IP 列表
85
+ - 获取回调 IP 列表
86
+ - 消息发送和素材管理
87
+ - 支持同步和异步两种调用方式
88
+
89
+ ## 依赖包
90
+
91
+ ```txt
92
+ httpx>=0.27.0
93
+ pydantic>=2.0
94
+ jsonpath-ng>=1.5.3
95
+ jsonschema>=4.21.0
96
+ diskcache>=5.6.3
97
+ redis>=4.6.0
98
+ py-httpx-toolkit>=1.0.1
99
+ ```
100
+
101
+ ## 安装方式
102
+
103
+ ```bash
104
+ pip install py-wecom-toolkit
105
+ ```
106
+ ### UV 安装
107
+
108
+ ```bash
109
+
110
+ uv add py-wecom-toolkit
111
+ ```
112
+
113
+
114
+ ## 项目结构
115
+
116
+ ```
117
+ src/py_wecom_toolkit/
118
+ ├── __init__.py # 主模块入口
119
+ ├── server/ # Server API 模块
120
+ │ ├── __init__.py # Base 类(access_token管理、IP查询)
121
+ │ ├── materials.py # 素材管理
122
+ │ ├── messages.py # 消息模型
123
+ │ ├── responses.py # 响应模型
124
+ │ └── utils.py # 工具函数
125
+ └── webhook/ # Webhook 模块
126
+ ├── __init__.py # Webhook 客户端类
127
+ ├── messages.py # 消息模型
128
+ ├── responses.py # 响应模型
129
+ └── utils.py # 工具函数
130
+ ```
131
+
132
+ ## 使用示例
133
+
134
+ ### Webhook 使用示例
135
+
136
+ **同步发送文本消息**:
137
+
138
+ ```python
139
+ from py_wecom_toolkit.webhook import Webhook
140
+ from py_wecom_toolkit.webhook.messages import Text, TextContent
141
+
142
+ # 初始化 Webhook
143
+ webhook = Webhook(key="your_webhook_key")
144
+
145
+ # 发送文本消息
146
+ response = webhook.send(Text(text=TextContent(content="Hello World")))
147
+ print(response.json())
148
+ ```
149
+
150
+ **异步发送消息**:
151
+
152
+ ```python
153
+ import asyncio
154
+ from py_wecom_toolkit.webhook import Webhook
155
+ from py_wecom_toolkit.webhook.messages import Markdown, MarkdownContent
156
+
157
+ async def main():
158
+ webhook = Webhook(key="your_webhook_key")
159
+ response = await webhook.async_send(
160
+ Markdown(markdown=MarkdownContent(content="**Hello** from async"))
161
+ )
162
+ print(response.json())
163
+
164
+ asyncio.run(main())
165
+ ```
166
+
167
+ **上传媒体文件**:
168
+
169
+ ```python
170
+ from py_wecom_toolkit.webhook import Webhook
171
+
172
+ webhook = Webhook(key="your_webhook_key")
173
+
174
+ with open("test.txt", "rb") as f:
175
+ response = webhook.upload_media(files={"file": ("test.txt", f)})
176
+ print(response.json()) # {"errcode": 0, "media_id": "xxx", ...}
177
+ ```
178
+
179
+ ### Server 使用示例
180
+
181
+ **获取 access_token**:
182
+
183
+ ```python
184
+ from py_wecom_toolkit.server import Base
185
+ from diskcache import Cache
186
+
187
+ # 创建缓存实例
188
+ cache = Cache("./cache")
189
+
190
+ # 初始化 Server 客户端
191
+ server = Base(
192
+ corpid="your_corpid",
193
+ corpsecret="your_corpsecret",
194
+ cache_config={"instance": cache}
195
+ )
196
+
197
+ # 刷新 access_token
198
+ server.refresh_access_token()
199
+ print(server.access_token)
200
+ ```
201
+
202
+ **异步获取 access_token**:
203
+
204
+ ```python
205
+ import asyncio
206
+ import redis
207
+ from py_wecom_toolkit.server import Base
208
+
209
+ async def main():
210
+ r = redis.Redis(host='localhost', port=6379, db=0)
211
+ server = Base(
212
+ corpid="your_corpid",
213
+ corpsecret="your_corpsecret",
214
+ cache_config={"instance": r}
215
+ )
216
+ await server.async_refresh_access_token()
217
+ print(server.access_token)
218
+
219
+ asyncio.run(main())
220
+ ```
221
+
222
+ **获取回调 IP**:
223
+
224
+ ```python
225
+ from py_wecom_toolkit.server import Base
226
+
227
+ server = Base(corpid="your_corpid", corpsecret="your_corpsecret")
228
+ server.refresh_access_token()
229
+ response = server.getcallbackip()
230
+ print(response.json())
231
+ ```
232
+
233
+ ## API 说明
234
+
235
+ ### Webhook 模块 API
236
+
237
+ #### Webhook 类
238
+
239
+ **初始化参数**:
240
+
241
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
242
+ |------|------|------|--------|------|
243
+ | key | str | 是 | - | Webhook密钥 |
244
+ | base_url | str | 否 | `https://qyapi.weixin.qq.com/` | 企业微信API基础URL |
245
+ | mentioned_list | list/tuple | 否 | `[]` | 默认@用户ID列表 |
246
+ | mentioned_mobile_list | list/tuple | 否 | `[]` | 默认@用户手机号列表 |
247
+ | client_kwargs | dict | 否 | `{}` | HTTP客户端配置参数 |
248
+
249
+ **方法**:
250
+
251
+ | 方法名 | 说明 | 参数 | 返回值 |
252
+ |--------|------|------|--------|
253
+ | `send(message)` | 同步发送消息 | `message`: 消息对象或字典 | `httpx.Response` |
254
+ | `async_send(message)` | 异步发送消息 | `message`: 消息对象或字典 | `await httpx.Response` |
255
+ | `upload_media(file_type, **kwargs)` | 同步上传媒体文件 | `file_type`: "file"或"voice",`files`: 文件参数 | `httpx.Response` |
256
+ | `async_upload_media(file_type, **kwargs)` | 异步上传媒体文件 | `file_type`: "file"或"voice",`files`: 文件参数 | `await httpx.Response` |
257
+
258
+ #### Webhook 消息类型
259
+
260
+ | 消息类型 | 类名 | 说明 |
261
+ |----------|------|------|
262
+ | 文本 | `Text` | 支持@用户,最长2048字节 |
263
+ | Markdown | `Markdown` | 支持标准Markdown语法 |
264
+ | MarkdownV2 | `MarkdownV2` | 支持字体颜色等扩展样式 |
265
+ | 图片 | `Image` | 需要base64编码和md5值 |
266
+ | 图文 | `News` | 支持1-8篇文章 |
267
+ | 文件 | `File` | 需要先上传获取media_id |
268
+ | 语音 | `Voice` | 需要先上传获取media_id |
269
+ | 模板卡片 | `TemplateCard` | 支持多种卡片类型 |
270
+
271
+ **文本消息示例**:
272
+
273
+ ```python
274
+ from py_wecom_toolkit.webhook.messages import Text, TextContent
275
+
276
+ message = Text(
277
+ text=TextContent(
278
+ content="Hello World",
279
+ mentioned_list=["user1", "@all"],
280
+ mentioned_mobile_list=["13800138000"]
281
+ )
282
+ )
283
+ ```
284
+
285
+ **图文消息示例**:
286
+
287
+ ```python
288
+ from py_wecom_toolkit.webhook.messages import News, NewsContent, NewsArticle
289
+
290
+ message = News(
291
+ news=NewsContent(
292
+ articles=[
293
+ NewsArticle(
294
+ title="文章标题",
295
+ description="文章描述",
296
+ url="https://example.com",
297
+ picurl="https://example.com/image.jpg"
298
+ )
299
+ ]
300
+ )
301
+ )
302
+ ```
303
+
304
+ ### Server 模块 API
305
+
306
+ #### Base 类
307
+
308
+ **初始化参数**:
309
+
310
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
311
+ |------|------|------|--------|------|
312
+ | corpid | str | 是 | - | 企业ID |
313
+ | corpsecret | str | 是 | - | 应用密钥 |
314
+ | agentid | str/int | 否 | - | 企业应用ID |
315
+ | base_url | str | 否 | `https://qyapi.weixin.qq.com/` | 企业微信API基础URL |
316
+ | cache_config | dict | 否 | `{}` | 缓存配置 |
317
+ | client_kwargs | dict | 否 | `{}` | HTTP客户端配置参数 |
318
+
319
+ **cache_config 参数**:
320
+
321
+ | 参数 | 类型 | 必填 | 默认值 | 说明 |
322
+ |------|------|------|--------|------|
323
+ | instance | Cache/Redis | 否 | `None` | 缓存实例(diskcache.Cache或redis.Redis) |
324
+ | key | str | 否 | `pywecom_server_{corpid}_{agentid}` | 缓存键名 |
325
+ | expire | int | 否 | `7100` | 缓存过期时间(秒) |
326
+
327
+ **方法**:
328
+
329
+ | 方法名 | 说明 | 参数 | 返回值 |
330
+ |--------|------|------|--------|
331
+ | `gettoken()` | 同步获取access_token | - | `httpx.Response` |
332
+ | `async_gettoken()` | 异步获取access_token | - | `await httpx.Response` |
333
+ | `get_api_domain_ip()` | 同步获取API域名IP | - | `httpx.Response` |
334
+ | `async_get_api_domain_ip()` | 异步获取API域名IP | - | `await httpx.Response` |
335
+ | `getcallbackip()` | 同步获取回调IP | - | `httpx.Response` |
336
+ | `async_getcallbackip()` | 异步获取回调IP | - | `await httpx.Response` |
337
+ | `refresh_access_token()` | 同步刷新access_token | `cache_config` | `self` |
338
+ | `async_refresh_access_token()` | 异步刷新access_token | `cache_config` | `await self` |
339
+
340
+ #### Sender 类
341
+
342
+ 继承自 `Base` 类,新增消息发送方法:
343
+
344
+ | 方法名 | 说明 | 参数 | 返回值 |
345
+ |--------|------|------|--------|
346
+ | `send(message)` | 同步发送消息 | `message`: 消息对象或字典 | `httpx.Response` |
347
+ | `async_send(message)` | 异步发送消息 | `message`: 消息对象或字典 | `await httpx.Response` |
348
+
349
+ #### Server 消息类型
350
+
351
+ | 消息类型 | 类名 | 说明 |
352
+ |----------|------|------|
353
+ | 文本 | `Text` | 支持@用户 |
354
+ | 图片 | `Image` | 需要media_id |
355
+ | 语音 | `Voice` | 需要media_id |
356
+ | 视频 | `Video` | 需要media_id |
357
+ | 文件 | `File` | 需要media_id |
358
+ | 文本卡片 | `TextCard` | 带标题和链接的卡片 |
359
+ | 图文 | `News` | 支持1-8篇文章 |
360
+ | 图文(mpnews) | `MpNews` | 素材库文章 |
361
+ | Markdown | `Markdown` | Markdown格式消息 |
362
+ | 小程序通知 | `MiniprogramNotice` | 小程序模板消息 |
363
+ | 模板卡片 | `TemplateCard` | 支持多种卡片类型 |
364
+
365
+ **发送文本消息示例**:
366
+
367
+ ```python
368
+ from py_wecom_toolkit.server.messages import Sender, Text, TextContent
369
+
370
+ sender = Sender(corpid="your_corpid", corpsecret="your_corpsecret", agentid=1000001)
371
+ sender.refresh_access_token()
372
+ response = sender.send(Text(text=TextContent(content="Hello")))
373
+ ```
374
+
375
+ **发送图文消息示例**:
376
+
377
+ ```python
378
+ from py_wecom_toolkit.server.messages import Sender, News, NewsArticlesContent, NewsArticleContent
379
+
380
+ sender = Sender(corpid="your_corpid", corpsecret="your_corpsecret", agentid=1000001)
381
+ sender.refresh_access_token()
382
+
383
+ message = News(
384
+ news=NewsArticlesContent(
385
+ articles=[
386
+ NewsArticleContent(
387
+ title="文章标题",
388
+ description="描述",
389
+ url="https://example.com",
390
+ picurl="https://example.com/image.jpg"
391
+ )
392
+ ]
393
+ )
394
+ )
395
+ response = sender.send(message)
396
+ ```
397
+
398
+ ### 响应模型
399
+
400
+ 响应结果可通过 `response.json()` 获取,企业微信API响应结构:
401
+
402
+ ```python
403
+ {
404
+ "errcode": 0, # 错误码,0表示成功
405
+ "errmsg": "ok", # 错误信息
406
+ "access_token": "...", # access_token(仅gettoken接口)
407
+ "media_id": "...", # 媒体文件ID(仅上传接口)
408
+ "ip_list": [...] # IP列表(仅IP查询接口)
409
+ }
410
+ ```
411
+
412
+ **错误码说明**:
413
+
414
+ | 错误码 | 说明 |
415
+ |--------|------|
416
+ | 0 | 成功 |
417
+ | 40001 | 获取access_token时AppSecret错误 |
418
+ | 40002 | 不合法的凭证类型 |
419
+ | 40003 | 不合法的UserID |
420
+ | 40014 | 不合法的access_token |
421
+ | 40033 | 不合法的字符 |
422
+ | 40056 | 不合法的agentid |
423
+ | 40060 | 不合法的media_id |
424
+ | 50000 | 企业不存在 |
425
+ | 60001 | 应用不存在 |
426
+ | 60002 | 应用已停用 |
427
+
428
+ ## 工具函数说明
429
+
430
+ ### Webhook 工具函数 (`py_wecom_toolkit.webhook.utils`)
431
+
432
+ | 函数名 | 说明 | 参数 | 返回值 |
433
+ |--------|------|------|--------|
434
+ | `json_find_first(expression, data)` | 使用JSONPath查找第一个匹配项 | `expression`: JSONPath表达式,`data`: JSON数据 | 匹配值或None |
435
+ | `json_is_valid(schema, data)` | JSON Schema校验 | `schema`: Schema字典,`data`: 待校验数据 | bool |
436
+ | `image_to_base64_and_md5(image_path)` | 图片转Base64和MD5 | `image_path`: 图片文件路径 | Tuple[str, str] |
437
+ | `build_success_instance(response)` | 转换为Success响应模型 | `response`: Response对象或dict | Success |
438
+ | `build_send_instance(response)` | 转换为Send响应模型 | `response`: Response对象或dict | Send |
439
+ | `build_upload_media_instance(response)` | 转换为UploadMedia响应模型 | `response`: Response对象或dict | UploadMedia |
440
+ | `success_validator(response, schema)` | 校验errcode是否为0 | `response`: Response对象或dict | bool |
441
+
442
+ **图片转换示例**:
443
+
444
+ ```python
445
+ from py_wecom_toolkit.webhook.utils import image_to_base64_and_md5
446
+ from py_wecom_toolkit.webhook.messages import Image, ImageContent
447
+
448
+ base64_str, md5_str = image_to_base64_and_md5("path/to/image.jpg")
449
+ message = Image(image=ImageContent(base64=base64_str, md5=md5_str))
450
+ ```
451
+
452
+ **响应校验示例**:
453
+
454
+ ```python
455
+ from py_wecom_toolkit.webhook import Webhook
456
+ from py_wecom_toolkit.webhook.utils import build_send_instance, success_validator
457
+
458
+ webhook = Webhook(key="your_key")
459
+ response = webhook.send(message)
460
+
461
+ # 校验响应
462
+ if success_validator(response):
463
+ result = build_send_instance(response)
464
+ print(f"发送成功: {result.errmsg}")
465
+ else:
466
+ print("发送失败")
467
+ ```
468
+
469
+ ### Server 工具函数 (`py_wecom_toolkit.server.utils`)
470
+
471
+ | 函数名 | 说明 | 参数 | 返回值 |
472
+ |--------|------|------|--------|
473
+ | `json_find_first(expression, data)` | 使用JSONPath查找第一个匹配项 | `expression`: JSONPath表达式,`data`: JSON数据 | 匹配值或None |
474
+ | `json_is_valid(schema, data)` | JSON Schema校验 | `schema`: Schema字典,`data`: 待校验数据 | bool |
475
+ | `build_success_instance(response)` | 转换为Success响应模型 | `response`: Response对象或dict | Success |
476
+ | `success_is_valid(response, schema)` | 校验errcode是否为0 | `response`: Response对象或dict | bool |
477
+
478
+ **响应校验示例**:
479
+
480
+ ```python
481
+ from py_wecom_toolkit.server.messages import Sender, Text, TextContent
482
+ from py_wecom_toolkit.server.utils import build_success_instance, success_is_valid
483
+
484
+ sender = Sender(corpid="your_corpid", corpsecret="your_corpsecret", agentid=1000001)
485
+ sender.refresh_access_token()
486
+ response = sender.send(Text(text=TextContent(content="Hello")))
487
+
488
+ # 校验响应
489
+ if success_is_valid(response):
490
+ result = build_success_instance(response)
491
+ print(f"发送成功: {result.errmsg}")
492
+ else:
493
+ print("发送失败")
494
+ ```
495
+
496
+ **JSONPath查找示例**:
497
+
498
+ ```python
499
+ from py_wecom_toolkit.server.utils import json_find_first
500
+
501
+ data = {
502
+ "result": {
503
+ "items": [
504
+ {"id": 1, "name": "item1"},
505
+ {"id": 2, "name": "item2"}
506
+ ]
507
+ }
508
+ }
509
+
510
+ # 使用JSONPath查找第一个item的name
511
+ name = json_find_first("$.result.items[0].name", data)
512
+ print(name) # item1
513
+ ```
514
+
515
+ ## 参考文档
516
+
517
+ - [企业微信 Webhook API](https://developer.work.weixin.qq.com/document/path/91770)
518
+ - [企业微信 Server API](https://developer.work.weixin.qq.com/document/path/90664)
519
+
520
+ ## 许可证
521
+
522
+ MIT License