py-wecom 0.1.0__py3-none-any.whl

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/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ from typing import List, Optional, Tuple, Union
4
+
5
+ import diskcache
6
+ import httpx
7
+ import redis
8
+ from pydantic import HttpUrl
9
+ from .utils import convert_to_errcode_eq_0, errcode_eq_0_validator
10
+
11
+
12
+ class Base:
13
+ def __init__(
14
+ self,
15
+ base_url: HttpUrl = "https://qyapi.weixin.qq.com",
16
+ corpid: Optional[str] = None,
17
+ corpsecret: Optional[str] = None,
18
+ agentid: Optional[Union[str, int]] = None,
19
+ cache_config: Optional[dict] = None,
20
+ client_kwargs: Optional[dict] = None,
21
+ ):
22
+ """
23
+ 初始化Server实例
24
+
25
+ 参数:
26
+ base_url: 企业微信API基础URL,默认"https://qyapi.weixin.qq.com"
27
+ corpid: 企业ID,可在企业微信管理后台获取
28
+ corpsecret: 应用密钥,可在企业微信管理后台获取
29
+ agentid: 企业应用ID,可在企业微信管理后台获取
30
+ cache_config: 缓存配置字典,包含instance(缓存实例)、key(缓存键名)、expire(过期时间)
31
+ client_kwargs: httpx客户端配置参数
32
+ """
33
+ self.base_url = base_url[:-1] if base_url.endswith("/") else base_url
34
+ self.corpid = corpid or ""
35
+ self.corpsecret = corpsecret or ""
36
+ self.agentid = agentid or ""
37
+ self.cache_config = cache_config or {}
38
+ self.cache_config = {
39
+ **{
40
+ "instance": None,
41
+ "key": f"pywecom_server_{self.corpid}_{self.agentid}",
42
+ "expire": 7100,
43
+ },
44
+ **self.cache_config,
45
+ }
46
+ self.client_kwargs = client_kwargs or {}
47
+ self.client_kwargs = {
48
+ **{
49
+ "base_url": self.base_url,
50
+ "timeout": 60,
51
+ },
52
+ **self.client_kwargs,
53
+ }
54
+ self.access_token = ""
55
+
56
+ def client(self) -> httpx.Client:
57
+ """
58
+ 创建并返回同步HTTP客户端
59
+
60
+ Returns:
61
+ httpx.Client: 配置好的同步HTTP客户端实例
62
+ """
63
+ return httpx.Client(**self.client_kwargs)
64
+
65
+ def async_client(self) -> httpx.AsyncClient:
66
+ """
67
+ 创建并返回异步HTTP客户端
68
+
69
+ Returns:
70
+ httpx.AsyncClient: 配置好的异步HTTP客户端实例
71
+ """
72
+ return httpx.AsyncClient(**self.client_kwargs)
73
+
74
+ def gettoken(
75
+ self,
76
+ client: Optional[httpx.Client] = None,
77
+ **kwargs
78
+ ) -> httpx.Response:
79
+ """
80
+ 获取企业微信access_token
81
+
82
+ @see:https://developer.work.weixin.qq.com/document/path/91039
83
+ Args:
84
+ client: 可选的HTTP客户端实例,若不提供则自动创建
85
+ **kwargs: 请求参数,包含url、method、json等
86
+
87
+ Returns:
88
+ httpx.Response: 发送响应结果,包含错误码和错误信息
89
+ """
90
+ kwargs = kwargs or {}
91
+ kwargs = {
92
+ **{
93
+ "url": "/cgi-bin/gettoken",
94
+ "method": "GET",
95
+ "params": {
96
+ "corpid": self.corpid,
97
+ "corpsecret": self.corpsecret,
98
+ }
99
+ },
100
+ **kwargs
101
+ }
102
+ if isinstance(client, httpx.Client):
103
+ response = client.request(**kwargs)
104
+ else:
105
+ with self.client() as _client:
106
+ response = _client.request(**kwargs)
107
+ return response
108
+
109
+ def get_api_domain_ip(
110
+ self,
111
+ client: Optional[httpx.Client] = None,
112
+ **kwargs
113
+ ) -> httpx.Response:
114
+ """
115
+ 获取企业微信API域名IP
116
+
117
+ @see:https://developer.work.weixin.qq.com/document/path/92520
118
+ Args:
119
+ client: 可选的HTTP客户端实例,若不提供则自动创建
120
+ **kwargs: 请求参数,包含url、method、json等
121
+
122
+ Returns:
123
+ httpx.Response: 发送响应结果,包含错误码和错误信息
124
+ """
125
+ kwargs = kwargs or {}
126
+ kwargs = {
127
+ **{
128
+ "url": "/cgi-bin/get_api_domain_ip",
129
+ "method": "GET",
130
+ "params": {
131
+ "access_token": self.access_token,
132
+ }
133
+ },
134
+ **kwargs
135
+ }
136
+ if isinstance(client, httpx.Client):
137
+ response = client.request(**kwargs)
138
+ else:
139
+ with self.client() as _client:
140
+ response = _client.request(**kwargs)
141
+ return response
142
+
143
+ def getcallbackip(
144
+ self,
145
+ client: Optional[httpx.Client] = None,
146
+ **kwargs
147
+ ) -> httpx.Response:
148
+ """
149
+ 获取企业微信回调IP
150
+
151
+ @see:https://developer.work.weixin.qq.com/document/path/92521
152
+ Args:
153
+ client: 可选的HTTP客户端实例,若不提供则自动创建
154
+ **kwargs: 请求参数,包含url、method、json等
155
+
156
+ Returns:
157
+ httpx.Response: 发送响应结果,包含错误码和错误信息
158
+ """
159
+ kwargs = kwargs or {}
160
+ kwargs = {
161
+ **{
162
+ "url": "/cgi-bin/getcallbackip",
163
+ "method": "GET",
164
+ "params": {
165
+ "access_token": self.access_token,
166
+ }
167
+ },
168
+ **kwargs
169
+ }
170
+ if isinstance(client, httpx.Client):
171
+ response = client.request(**kwargs)
172
+ else:
173
+ with self.client() as _client:
174
+ response = _client.request(**kwargs)
175
+ return response
176
+
177
+ def refresh_access_token(
178
+ self,
179
+ gettoken_func_kwargs: Optional[dict] = None,
180
+ get_api_domain_ip_func_kwargs: Optional[dict] = None
181
+ ) -> "Base":
182
+ gettoken_func_kwargs = gettoken_func_kwargs or {}
183
+ get_api_domain_ip_func_kwargs = get_api_domain_ip_func_kwargs or {}
184
+ cache_key = self.cache_config.get("key", f"pywecom_server_{self.corpid}_{self.agentid}")
185
+ cache_expire = self.cache_config.get("expire", 7100)
186
+ cache_instance = self.cache_config.get("instance", None)
187
+ if not isinstance(cache_instance, Union[diskcache.Cache, redis.Redis]):
188
+ self.access_token = convert_to_errcode_eq_0(
189
+ self.gettoken(**gettoken_func_kwargs)
190
+ ).access_token
191
+ else:
192
+ self.access_token = cache_instance.get(cache_key)
193
+ if not isinstance(self.access_token, str) or not len(self.access_token):
194
+ self.access_token = convert_to_errcode_eq_0(
195
+ self.gettoken(**gettoken_func_kwargs)
196
+ ).access_token
197
+ ip_list = convert_to_errcode_eq_0(
198
+ self.get_api_domain_ip(**get_api_domain_ip_func_kwargs)
199
+ ).ip_list
200
+ if not isinstance(ip_list, list):
201
+ self.access_token = convert_to_errcode_eq_0(
202
+ self.gettoken(**gettoken_func_kwargs)
203
+ ).access_token
204
+ ip_list = convert_to_errcode_eq_0(
205
+ self.get_api_domain_ip(**get_api_domain_ip_func_kwargs)
206
+ ).ip_list
207
+ if isinstance(ip_list, list):
208
+ if isinstance(cache_instance, diskcache.Cache):
209
+ cache_instance.set(cache_key, self.access_token, expire=cache_expire)
210
+ elif isinstance(cache_instance, redis.Redis):
211
+ cache_instance.set(cache_key, self.access_token, ex=cache_expire)
212
+ return self
213
+
214
+ async def async_gettoken(
215
+ self,
216
+ client: Optional[httpx.AsyncClient] = None,
217
+ **kwargs
218
+ ) -> httpx.Response:
219
+ """
220
+ 获取企业微信access_token
221
+
222
+ @see:https://developer.work.weixin.qq.com/document/path/91039
223
+ Args:
224
+ client: 可选的HTTP客户端实例,若不提供则自动创建
225
+ **kwargs: 请求参数,包含url、method、json等
226
+
227
+ Returns:
228
+ httpx.Response: 发送响应结果,包含错误码和错误信息
229
+ """
230
+ kwargs = kwargs or {}
231
+ kwargs = {
232
+ **{
233
+ "url": "/cgi-bin/gettoken",
234
+ "method": "GET",
235
+ "params": {
236
+ "corpid": self.corpid,
237
+ "corpsecret": self.corpsecret,
238
+ }
239
+ },
240
+ **kwargs
241
+ }
242
+ if isinstance(client, httpx.AsyncClient):
243
+ response = await client.request(**kwargs)
244
+ else:
245
+ async with self.async_client() as _client:
246
+ response = await _client.request(**kwargs)
247
+ return response
248
+
249
+ async def async_get_api_domain_ip(
250
+ self,
251
+ client: Optional[httpx.AsyncClient] = None,
252
+ **kwargs
253
+ ) -> httpx.Response:
254
+ """
255
+ 获取企业微信API域名IP
256
+
257
+ @see:https://developer.work.weixin.qq.com/document/path/92520
258
+ Args:
259
+ client: 可选的HTTP客户端实例,若不提供则自动创建
260
+ **kwargs: 请求参数,包含url、method、json等
261
+
262
+ Returns:
263
+ httpx.Response: 发送响应结果,包含错误码和错误信息
264
+ """
265
+ kwargs = kwargs or {}
266
+ kwargs = {
267
+ **{
268
+ "url": "/cgi-bin/get_api_domain_ip",
269
+ "method": "GET",
270
+ "params": {
271
+ "access_token": self.access_token,
272
+ }
273
+ },
274
+ **kwargs
275
+ }
276
+ if isinstance(client, httpx.Client):
277
+ response = client.request(**kwargs)
278
+ else:
279
+ async with self.async_client() as _client:
280
+ response = await _client.request(**kwargs)
281
+ return response
282
+
283
+ async def async_getcallbackip(
284
+ self,
285
+ client: Optional[httpx.AsyncClient] = None,
286
+ **kwargs
287
+ ) -> httpx.Response:
288
+ """
289
+ 获取企业微信回调IP
290
+
291
+ @see:https://developer.work.weixin.qq.com/document/path/92521
292
+ Args:
293
+ client: 可选的HTTP客户端实例,若不提供则自动创建
294
+ **kwargs: 请求参数,包含url、method、json等
295
+
296
+ Returns:
297
+ httpx.Response: 发送响应结果,包含错误码和错误信息
298
+ """
299
+ kwargs = kwargs or {}
300
+ kwargs = {
301
+ **{
302
+ "url": "/cgi-bin/getcallbackip",
303
+ "method": "GET",
304
+ "params": {
305
+ "access_token": self.access_token,
306
+ }
307
+ },
308
+ **kwargs
309
+ }
310
+ if isinstance(client, httpx.AsyncClient):
311
+ response = await client.request(**kwargs)
312
+ else:
313
+ async with self.async_client() as _client:
314
+ response = await _client.request(**kwargs)
315
+ return response
316
+
317
+ async def async_refresh_access_token(
318
+ self,
319
+ gettoken_func_kwargs: Optional[dict] = None,
320
+ get_api_domain_ip_func_kwargs: Optional[dict] = None
321
+ ) -> "Base":
322
+ gettoken_func_kwargs = gettoken_func_kwargs or {}
323
+ get_api_domain_ip_func_kwargs = get_api_domain_ip_func_kwargs or {}
324
+ cache_key = self.cache_config.get("key", f"pywecom_server_{self.corpid}_{self.agentid}")
325
+ cache_expire = self.cache_config.get("expire", 7100)
326
+ cache_instance = self.cache_config.get("instance", None)
327
+ if not isinstance(cache_instance, Union[diskcache.Cache, redis.Redis]):
328
+ self.access_token = convert_to_errcode_eq_0(
329
+ await self.async_gettoken(**gettoken_func_kwargs)
330
+ ).access_token
331
+ else:
332
+ self.access_token = cache_instance.get(cache_key)
333
+ if not isinstance(self.access_token, str) or not len(self.access_token):
334
+ self.access_token = convert_to_errcode_eq_0(
335
+ await self.async_gettoken(**gettoken_func_kwargs)
336
+ ).access_token
337
+ ip_list = convert_to_errcode_eq_0(
338
+ await self.async_get_api_domain_ip(**get_api_domain_ip_func_kwargs)
339
+ ).ip_list
340
+ if not isinstance(ip_list, list):
341
+ self.access_token = convert_to_errcode_eq_0(
342
+ await self.async_gettoken(**gettoken_func_kwargs)
343
+ ).access_token
344
+ ip_list = convert_to_errcode_eq_0(
345
+ await self.async_get_api_domain_ip(**get_api_domain_ip_func_kwargs)
346
+ ).ip_list
347
+ if isinstance(ip_list, list):
348
+ if isinstance(cache_instance, diskcache.Cache):
349
+ cache_instance.set(cache_key, self.access_token, expire=cache_expire)
350
+ elif isinstance(cache_instance, redis.Redis):
351
+ cache_instance.set(cache_key, self.access_token, ex=cache_expire)
352
+ return self
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: UTF-8 -*-
3
+ """
4
+ 企业微信素材上传模块
5
+
6
+ 该模块提供了企业微信素材上传的同步和异步方法,支持上传图片、视频、语音和普通文件。
7
+ """
8
+
9
+ from typing import Optional
10
+
11
+ import httpx
12
+
13
+ from . import Base
14
+
15
+
16
+ class Uploader(Base):
17
+ """
18
+ 企业微信素材上传器类
19
+
20
+ 继承自Base类,提供同步和异步的素材上传功能,支持多种文件类型的上传。
21
+ """
22
+
23
+ def upload(self, client: Optional[httpx.Client] = None, file_type: Optional[str] = "file", **kwargs):
24
+ """
25
+ 同步上传临时素材
26
+
27
+ 调用企业微信API /cgi-bin/media/upload 上传临时素材,有效期为3天。
28
+
29
+ @see:https://developer.work.weixin.qq.com/document/path/90253
30
+ Args:
31
+ client: 可选的httpx.Client实例,用于复用连接。如果不传,将自动创建新的client。
32
+ file_type: 上传文件类型,支持 image、video、voice、file,默认为 file。
33
+ 若传入的类型不在支持列表中,将自动转为 file。
34
+ **kwargs: 额外的请求参数,将与默认参数合并。
35
+
36
+ Returns:
37
+ httpx.Response: API响应对象,包含上传后的素材ID等信息。
38
+ """
39
+ # 验证文件类型,只支持指定的四种类型
40
+ file_type = "file" if file_type.lower() not in ["image", "video", "voice", "file"] else file_type
41
+ kwargs = kwargs or {}
42
+ kwargs = {
43
+ **{
44
+ "url": "/cgi-bin/media/upload",
45
+ "method": "POST",
46
+ "params": {
47
+ "access_token": self.access_token,
48
+ "type": file_type,
49
+ }
50
+ },
51
+ **kwargs
52
+ }
53
+ if isinstance(client, httpx.Client):
54
+ response = client.request(**kwargs)
55
+ else:
56
+ with self.client() as _client:
57
+ response = _client.request(**kwargs)
58
+ return response
59
+
60
+ def uploadimg(self, client: Optional[httpx.Client] = None, **kwargs):
61
+ """
62
+ 同步上传图片素材
63
+
64
+ 调用企业微信API /cgi-bin/media/uploadimg 上传图片,返回的图片URL永久有效。
65
+
66
+ @see:https://developer.work.weixin.qq.com/document/path/90256
67
+ Args:
68
+ client: 可选的httpx.Client实例,用于复用连接。如果不传,将自动创建新的client。
69
+ **kwargs: 额外的请求参数,将与默认参数合并。
70
+
71
+ Returns:
72
+ httpx.Response: API响应对象,包含图片URL等信息。
73
+ """
74
+ kwargs = kwargs or {}
75
+ kwargs = {
76
+ **{
77
+ "url": "/cgi-bin/media/uploadimg",
78
+ "method": "POST",
79
+ "params": {
80
+ "access_token": self.access_token,
81
+ }
82
+ },
83
+ **kwargs
84
+ }
85
+ if isinstance(client, httpx.Client):
86
+ response = client.request(**kwargs)
87
+ else:
88
+ with self.client() as _client:
89
+ response = _client.request(**kwargs)
90
+ return response
91
+
92
+ async def async_upload(self, client: Optional[httpx.AsyncClient] = None, file_type: Optional[str] = "file",
93
+ **kwargs):
94
+ """
95
+ 异步上传临时素材
96
+
97
+ 调用企业微信API /cgi-bin/media/upload 异步上传临时素材,有效期为3天。
98
+
99
+ @see:https://developer.work.weixin.qq.com/document/path/90253
100
+ Args:
101
+ client: 可选的httpx.AsyncClient实例,用于复用连接。如果不传,将自动创建新的client。
102
+ file_type: 上传文件类型,支持 image、video、voice、file,默认为 file。
103
+ 若传入的类型不在支持列表中,将自动转为 file。
104
+ **kwargs: 额外的请求参数,将与默认参数合并。
105
+
106
+ Returns:
107
+ httpx.Response: API响应对象,包含上传后的素材ID等信息。
108
+ """
109
+ # 验证文件类型,只支持指定的四种类型
110
+ file_type = "file" if file_type.lower() not in ["image", "video", "voice", "file"] else file_type
111
+ kwargs = kwargs or {}
112
+ kwargs = {
113
+ **{
114
+ "url": "/cgi-bin/media/upload",
115
+ "method": "POST",
116
+ "params": {
117
+ "access_token": self.access_token,
118
+ "type": file_type,
119
+ }
120
+ },
121
+ **kwargs
122
+ }
123
+ if isinstance(client, httpx.AsyncClient):
124
+ response = await client.request(**kwargs)
125
+ else:
126
+ async with self.async_client() as _client:
127
+ response = await _client.request(**kwargs)
128
+ return response
129
+
130
+ async def async_uploadimg(self, client: Optional[httpx.AsyncClient] = None, **kwargs):
131
+ """
132
+ 异步上传图片素材
133
+
134
+ 调用企业微信API /cgi-bin/media/uploadimg 异步上传图片,返回的图片URL永久有效。
135
+
136
+ @see:https://developer.work.weixin.qq.com/document/path/90256
137
+ Args:
138
+ client: 可选的httpx.AsyncClient实例,用于复用连接。如果不传,将自动创建新的client。
139
+ **kwargs: 额外的请求参数,将与默认参数合并。
140
+
141
+ Returns:
142
+ httpx.Response: API响应对象,包含图片URL等信息。
143
+ """
144
+ kwargs = kwargs or {}
145
+ kwargs = {
146
+ **{
147
+ "url": "/cgi-bin/media/uploadimg",
148
+ "method": "POST",
149
+ "params": {
150
+ "access_token": self.access_token,
151
+ }
152
+ },
153
+ **kwargs
154
+ }
155
+ if isinstance(client, httpx.AsyncClient):
156
+ response = await client.request(**kwargs)
157
+ else:
158
+ async with self.async_client() as _client:
159
+ response = await _client.request(**kwargs)
160
+ return response