dagster-dingtalk 0.1.23__py3-none-any.whl → 0.1.26__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.
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import pickle
3
+ import threading
3
4
  import time
4
5
  from datetime import datetime
5
6
  from enum import Enum
@@ -13,99 +14,180 @@ from pydantic import BaseModel, Field
13
14
 
14
15
  # noinspection NonAsciiCharacters
15
16
  class DingTalkClient:
16
- def __init__(self, app_id: str, client_id: str, client_secret: str, app_name: str|None = None, agent_id: int|None = None):
17
+ def __init__(
18
+ self,
19
+ app_id: str,
20
+ client_id: str,
21
+ client_secret: str,
22
+ app_name: str|None = None,
23
+ agent_id: int|None = None,
24
+ ):
17
25
  self.app_id: str = app_id
18
26
  self.app_name: str|None = app_name
19
27
  self.agent_id: int|None = agent_id
20
- self.client_id: str = client_id
21
- self.__client_secret: str = client_secret
22
28
  self.robot_code: str = client_id
29
+ self.__client_id: str = client_id
30
+ self.__client_secret: str = client_secret
31
+
32
+ # 令牌缓存信息
33
+ self.__access_token_file_cache = Path.home() / f".dagster_dingtalk_cache_{self.app_id}"
34
+ self.__access_token_cache: dict = {}
35
+ self.__token_lock = threading.Lock()
36
+
37
+ # 初始化持久化 HTTP 客户端
38
+ self.__init_clients()
39
+
40
+ # API模块
41
+ self.智能人事 = 智能人事__(self)
42
+ self.通讯录管理 = 通讯录管理__(self)
43
+ self.文档文件 = 文档文件__(self)
44
+ self.互动卡片 = 互动卡片__(self)
45
+ self.OA审批 = OA审批__(self)
46
+ self.即时通信 = 即时通信__(self)
47
+
48
+
49
+ def __init_clients(self):
50
+ self.__oapi_client = httpx.Client(
51
+ base_url="https://oapi.dingtalk.com/",
52
+ timeout=httpx.Timeout(60.0),
53
+ limits=httpx.Limits(max_connections=100),
54
+ )
23
55
 
24
- access_token: str = self.__get_access_token()
25
- self.api: Client = Client(base_url="https://api.dingtalk.com/", headers={"x-acs-dingtalk-access-token": access_token})
26
- self.oapi: Client = Client(base_url="https://oapi.dingtalk.com/", params={"access_token": access_token})
27
-
28
- self.智能人事 = 智能人事_API(self)
29
- self.通讯录管理 = 通讯录管理_API(self)
30
- self.文档文件 = 文档文件_API(self)
31
- self.互动卡片 = 互动卡片_API(self)
32
- self.OA审批 = OA审批_API(self)
33
- self.即时通信 = 即时通信_API(self)
56
+ self.__api_client = httpx.Client(
57
+ base_url="https://api.dingtalk.com/",
58
+ timeout=httpx.Timeout(10.0),
59
+ limits=httpx.Limits(max_connections=100),
60
+ )
34
61
 
35
- def __get_access_token(self) -> str:
36
- access_token_cache = Path.home() / ".dagster_dingtalk_cache"
37
- all_access_token: dict = {}
38
- access_token: str|None = None
39
- expire_in: int = 0
40
- renew_reason = None
41
-
42
- # 从缓存中读取
43
- if not access_token_cache.exists():
44
- renew_reason = f"鉴权缓存不存在"
62
+ def __file_cache_read(self) -> dict:
63
+ if self.__access_token_file_cache.exists():
64
+ try:
65
+ with open(self.__access_token_file_cache, 'rb') as f:
66
+ return pickle.loads(f.read())
67
+ except Exception as e:
68
+ logging.warning(F"不存在 AccessToken 缓存或解析错误:{e}")
69
+ return {}
45
70
  else:
71
+ return {}
72
+
73
+ def __file_cache_write(self):
74
+ with self.__token_lock:
46
75
  try:
47
- with open(access_token_cache, 'rb') as f:
48
- all_access_token = pickle.loads(f.read())
76
+ with open(self.__access_token_file_cache, 'wb') as f:
77
+ f.write(pickle.dumps(self.__access_token_cache))
49
78
  except Exception as e:
50
- logging.error(e)
51
- renew_reason = "鉴权缓存读取或解析错误"
79
+ logging.error(f"AccessToken 缓存写入失败: {e}")
52
80
 
53
- if all_access_token:
54
- app_access_token = all_access_token.get(self.app_id)
55
- access_token = app_access_token.get('access_token')
56
- expire_in = app_access_token.get('expire_in')
57
- else:
58
- renew_reason = f"鉴权缓存不存在该应用 {self.app_name}<{self.app_id}>"
81
+ def __get_access_token(self) -> str:
82
+ with self.__token_lock:
83
+ # 如果实例属性中的缓存不存在,进一步尝试从文件缓存读取。
84
+ access_token_cache: dict = self.__access_token_cache or self.__file_cache_read()
85
+ access_token = access_token_cache.get('access_token')
86
+ expire_in = access_token_cache.get('expire_in', -1)
87
+
88
+ # 如果缓存不存在,或者缓存过期,则获取新token
89
+ if not access_token or expire_in < int(time.time()):
90
+ try:
91
+ response = Client().post(
92
+ url="https://api.dingtalk.com/v1.0/oauth2/accessToken",
93
+ json={"appKey": self.__client_id, "appSecret": self.__client_secret},
94
+ ).json()
95
+ # 提前 1 分钟进行续期
96
+ self.__access_token_cache = {
97
+ "access_token": response.get("accessToken"),
98
+ "expire_in": response.get("expireIn") + int(time.time()) - 60
99
+ }
100
+ self.__file_cache_write()
101
+ except Exception as e:
102
+ logging.error(f"AccessToken 获取失败: {e}")
103
+ raise
104
+
105
+ return self.__access_token_cache["access_token"]
106
+
107
+ def oapi(self, method: str, path: str, **kwargs) -> httpx.Response:
108
+ params = kwargs.get("params", {})
109
+ params["access_token"] = self.__get_access_token()
110
+ return self.__oapi_client.request(
111
+ method=method.upper(),
112
+ url=path,
113
+ params=params,
114
+ **kwargs
115
+ )
59
116
 
60
- if not access_token:
61
- renew_reason = F"应用 {self.app_name}<{self.app_id}> 的鉴权缓存无效"
62
- if expire_in < int(time.time()):
63
- renew_reason = F"应用 {self.app_name}<{self.app_id}> 的鉴权缓存过期"
117
+ def api(self, method: str, path: str, **kwargs) -> httpx.Response:
118
+ headers = kwargs.get("headers", {})
119
+ headers["x-acs-dingtalk-access-token"] = self.__get_access_token()
120
+ return self.__api_client.request(
121
+ method=method.upper(),
122
+ url=path,
123
+ headers=headers,
124
+ **kwargs
125
+ )
64
126
 
65
- if renew_reason is None:
66
- return access_token
67
- else:
68
- logging.warning(renew_reason)
69
- response = Client().post(
70
- url="https://api.dingtalk.com/v1.0/oauth2/accessToken",
71
- json={"appKey": self.client_id, "appSecret": self.__client_secret},
72
- )
73
- access_token:str = response.json().get("accessToken")
74
- expire_in:int = response.json().get("expireIn") + int(time.time()) - 60
75
- with open(access_token_cache, 'wb') as f:
76
- all_access_token[self.app_id] = {
77
- "access_token": access_token,
78
- "expire_in": expire_in,
79
- }
80
- f.write(pickle.dumps(all_access_token))
81
- return access_token
127
+ def __del__(self):
128
+ self.__oapi_client.close()
129
+ self.__api_client.close()
82
130
 
83
- # noinspection NonAsciiCharacters
84
- class 智能人事_API:
131
+
132
+
133
+ # 智能人事模块
134
+ # https://open.dingtalk.com/document/orgapp/intelligent-personnel-call-description
135
+ # todo 职位管理/获取企业职位列表
136
+ # todo 职位管理/获取企业职级列表
137
+ # todo 职位管理/获取企业职务列表
138
+ # todo 花名册/新增或删除花名册选项类型字段的选项
139
+ # todo 员工管理/添加待入职员工
140
+ # todo 员工管理/修改已离职员工信息
141
+ # todo 员工管理/员工加入待离职
142
+ # todo 员工管理/撤销员工待离职
143
+ # todo 员工管理/更新待离职员工离职信息
144
+ # todo 员工管理/获取企业已有的所有离职原因
145
+ # todo 员工关系/智能人事员工调岗
146
+ # todo 员工关系/确认员工离职并删除
147
+ # todo 员工关系/智能人事员工转正
148
+
149
+
150
+ # noinspection NonAsciiCharacters, PyPep8Naming
151
+ class 智能人事__:
85
152
  def __init__(self, _client:DingTalkClient):
86
153
  self.__client:DingTalkClient = _client
87
- self.花名册 = 智能人事_花名册_API(_client)
88
- self.员工管理 = 智能人事_员工管理_API(_client)
154
+ self.花名册 = 智能人事__花名册(_client)
155
+ self.员工管理 = 智能人事__员工管理(_client)
89
156
 
90
- # noinspection NonAsciiCharacters
91
- class 智能人事_花名册_API:
157
+
158
+ # noinspection NonAsciiCharacters, PyPep8Naming
159
+ class 智能人事__花名册:
92
160
  def __init__(self, _client:DingTalkClient):
93
161
  self.__client:DingTalkClient = _client
94
162
 
95
163
  def 获取花名册元数据(self) -> dict:
96
- response = self.__client.oapi.post(
97
- url="/topapi/smartwork/hrm/roster/meta/get",
164
+ response = self.__client.oapi(
165
+ method="POST",
166
+ path="/topapi/smartwork/hrm/roster/meta/get",
98
167
  json={"agentid": self.__client.agent_id},
99
168
  )
100
169
  return response.json()
101
170
 
102
171
  def 获取花名册字段组详情(self) -> dict:
103
- response = self.__client.oapi.post(
104
- url="/topapi/smartwork/hrm/employee/field/grouplist",
172
+ response = self.__client.oapi(
173
+ method="POST",
174
+ path="/topapi/smartwork/hrm/employee/field/grouplist",
105
175
  json={"agentid": self.__client.agent_id},
106
176
  )
107
177
  return response.json()
108
178
 
179
+ def 获取员工花名册字段信息(self, user_id_list:List[str], field_filter_list:List[str]|None = None, text_to_select_convert:bool|None = None) -> dict:
180
+ body_dict = {"userIdList": user_id_list, "appAgentId": self.__client.agent_id}
181
+ if field_filter_list is not None:
182
+ body_dict["fieldFilterList"] = field_filter_list
183
+ if text_to_select_convert is not None:
184
+ body_dict["text2SelectConvert"] = text_to_select_convert
185
+
186
+ response = self.__client.api(
187
+ method="POST",
188
+ path="/topapi/smartwork/hrm/roster/meta/get", json=body_dict, )
189
+ return response.json()
190
+
109
191
  def 更新员工花名册信息(self, user_id: str, groups: List[dict]) -> dict:
110
192
  """
111
193
  花名册分组数据结构查看 https://open.dingtalk.com/document/orgapp/intelligent-personnel-update-employee-file-information
@@ -113,8 +195,9 @@ class 智能人事_花名册_API:
113
195
  :param groups: 花名册分组
114
196
  :return:
115
197
  """
116
- response = self.__client.oapi.post(
117
- url="/topapi/smartwork/hrm/employee/v2/update",
198
+ response = self.__client.oapi(
199
+ method="POST",
200
+ path="/topapi/smartwork/hrm/employee/v2/update",
118
201
  json={
119
202
  "agentid": self.__client.agent_id,
120
203
  "param": {"groups": groups},
@@ -123,22 +206,13 @@ class 智能人事_花名册_API:
123
206
  )
124
207
  return response.json()
125
208
 
126
- def 获取员工花名册字段信息(self, user_id_list:List[str], field_filter_list:List[str]|None = None, text_to_select_convert:bool|None = None) -> dict:
127
- body_dict = {"userIdList": user_id_list, "appAgentId": self.__client.agent_id}
128
- if field_filter_list is not None:
129
- body_dict["fieldFilterList"] = field_filter_list
130
- if text_to_select_convert is not None:
131
- body_dict["text2SelectConvert"] = text_to_select_convert
132
209
 
133
- response = self.__client.api.post(url="/v1.0/hrm/rosters/lists/query", json=body_dict, )
134
- return response.json()
135
-
136
- # noinspection NonAsciiCharacters
137
- class 智能人事_员工管理_API:
210
+ # noinspection NonAsciiCharacters, PyPep8Naming
211
+ class 智能人事__员工管理:
138
212
  def __init__(self, _client:DingTalkClient):
139
213
  self.__client:DingTalkClient = _client
140
214
 
141
- # noinspection NonAsciiCharacters
215
+ # noinspection NonAsciiCharacters, PyPep8Naming
142
216
  class 在职员工状态(Enum):
143
217
  试用期: '2'
144
218
  正式: '3'
@@ -146,70 +220,89 @@ class 智能人事_员工管理_API:
146
220
  无状态: '-1'
147
221
 
148
222
  def 获取待入职员工列表(self, offset:int, size:int) -> dict:
149
- response = self.__client.oapi.post(
150
- "/topapi/smartwork/hrm/employee/querypreentry",
223
+ response = self.__client.oapi(
224
+ method="POST",
225
+ path="/topapi/smartwork/hrm/employee/querypreentry",
151
226
  json={"offset": offset, "size": size},
152
227
  )
153
228
  return response.json()
154
229
 
155
230
  def 获取在职员工列表(self, status_list:List[在职员工状态], offset:int, size:int) -> dict:
156
- response = self.__client.oapi.post(
157
- "/topapi/smartwork/hrm/employee/querypreentry",
231
+ response = self.__client.oapi(
232
+ method="POST",
233
+ path="/topapi/smartwork/hrm/employee/querypreentry",
158
234
  json={"status_list": status_list, "offset": offset, "size": size},
159
235
  )
160
236
  return response.json()
161
237
 
162
238
  def 获取离职员工列表(self, next_token:int, max_results:int) -> dict:
163
- response = self.__client.api.get(
164
- "/v1.0/hrm/employees/dismissions",
239
+ response = self.__client.api(
240
+ method="GET",
241
+ path="/v1.0/hrm/employees/dismissions",
165
242
  params={"nextToken": next_token, "maxResults": max_results},
166
243
  )
167
244
  return response.json()
168
245
 
169
246
  def 批量获取员工离职信息(self, user_id_list:List[str]) -> dict:
170
- response = self.__client.api.get(
171
- "/v1.0/hrm/employees/dimissionInfo",
247
+ response = self.__client.api(
248
+ method="GET",
249
+ path="/v1.0/hrm/employees/dimissionInfo",
172
250
  params={"userIdList": user_id_list},
173
251
  )
174
252
  return response.json()
175
253
 
176
- # noinspection NonAsciiCharacters
177
- class 通讯录管理_API:
254
+
255
+ # 通讯录管理模块
256
+ # https://open.dingtalk.com/document/orgapp/contacts-overview
257
+ # todo
258
+
259
+
260
+ # noinspection NonAsciiCharacters, PyPep8Naming
261
+ class 通讯录管理__:
178
262
  def __init__(self, _client:DingTalkClient):
179
263
  self.__client:DingTalkClient = _client
180
- self.用户管理 = 通讯录管理_用户管理_API(_client)
181
- self.部门管理 = 通讯录管理_部门管理_API(_client)
264
+ self.用户管理 = 通讯录管理__用户管理(_client)
265
+ self.部门管理 = 通讯录管理__部门管理(_client)
182
266
 
183
267
  def 查询用户详情(self, user_id:str, language:str = "zh_CN"):
184
268
  return self.用户管理.查询用户详情(user_id, language)
185
269
 
186
- # noinspection NonAsciiCharacters
187
- class 通讯录管理_用户管理_API:
270
+
271
+ # noinspection NonAsciiCharacters, PyPep8Naming
272
+ class 通讯录管理__用户管理:
188
273
  def __init__(self, _client:DingTalkClient):
189
274
  self.__client:DingTalkClient = _client
190
275
 
191
276
  def 查询用户详情(self, user_id:str, language:str = "zh_CN") -> dict:
192
- response = self.__client.oapi.post(url="/topapi/v2/user/get", json={"language": language, "userid": user_id})
277
+ response = self.__client.oapi(
278
+ method="POST",
279
+ path="/topapi/v2/user/get", json={"language": language, "userid": user_id})
193
280
  return response.json()
194
281
 
195
282
  def 查询离职记录列表(self, start_time:datetime, end_time:datetime|None, next_token:str, max_results:int) -> dict:
196
283
  params = {"startTime": start_time.strftime("%Y-%m-%dT%H:%M:%SZ"), "nextToken": next_token, "maxResults": max_results}
197
284
  if end_time is not None:
198
285
  params["endTime"] = end_time.strftime("%Y-%m-%dT%H:%M:%SZ")
199
- response = self.__client.api.get(url="/v1.0/contact/empLeaveRecords", params=params)
286
+ response = self.__client.api(
287
+ method="GET",
288
+ path="/v1.0/contact/empLeaveRecords", params=params)
200
289
  return response.json()
201
290
 
202
291
  def 获取部门用户userid列表(self, dept_id: int):
203
- response = self.__client.oapi.post(url="/topapi/user/listid", json={"dept_id": dept_id})
292
+ response = self.__client.oapi(
293
+ method="POST",
294
+ path="/topapi/user/listid", json={"dept_id": dept_id})
204
295
  return response.json()
205
296
 
206
297
  def 根据unionid获取用户userid(self, unionid: str):
207
- response = self.__client.oapi.post(url="/topapi/user/getbyunionid", json={"unionid": unionid})
298
+ response = self.__client.oapi(
299
+ method="POST",
300
+ path="/topapi/user/getbyunionid", json={"unionid": unionid})
208
301
  return response.json()
209
302
 
210
303
 
211
- # noinspection NonAsciiCharacters
212
- class 通讯录管理_部门管理_API:
304
+ # noinspection NonAsciiCharacters, PyPep8Naming
305
+ class 通讯录管理__部门管理:
213
306
  def __init__(self, _client:DingTalkClient):
214
307
  self.__client:DingTalkClient = _client
215
308
 
@@ -222,8 +315,9 @@ class 通讯录管理_部门管理_API:
222
315
  :param dept_id: 部门 ID ,根部门 ID 为 1。
223
316
  :param language: 通讯录语言。zh_CN en_US
224
317
  """
225
- response = self.__client.oapi.post(
226
- url="/topapi/v2/department/get",
318
+ response = self.__client.oapi(
319
+ method="POST",
320
+ path="/topapi/v2/department/get",
227
321
  json={"language": language, "dept_id": dept_id}
228
322
  )
229
323
  return response.json()
@@ -237,8 +331,9 @@ class 通讯录管理_部门管理_API:
237
331
  :param dept_id: 部门 ID ,根部门 ID 为 1。
238
332
  :param language: 通讯录语言。zh_CN en_US
239
333
  """
240
- response = self.__client.oapi.post(
241
- url="/topapi/v2/department/listsub",
334
+ response = self.__client.oapi(
335
+ method="POST",
336
+ path="/topapi/v2/department/listsub",
242
337
  json={"language": language, "dept_id": dept_id}
243
338
  )
244
339
  return response.json()
@@ -251,21 +346,157 @@ class 通讯录管理_部门管理_API:
251
346
 
252
347
  :param dept_id: 部门 ID ,根部门 ID 为 1。
253
348
  """
254
- response = self.__client.oapi.post(
255
- url="/topapi/v2/department/listsubid",
349
+ response = self.__client.oapi(
350
+ method="POST",
351
+ path="/topapi/v2/department/listsubid",
256
352
  json={"dept_id": dept_id}
257
353
  )
258
354
  return response.json()
259
355
 
260
- # noinspection NonAsciiCharacters
261
- class 文档文件_API:
356
+
357
+ # 待办任务模块
358
+ # https://open.dingtalk.com/document/orgapp/dingtalk-todo-task-overview
359
+ # todo
360
+
361
+
362
+ # noinspection NonAsciiCharacters, PyPep8Naming
363
+ class 待办任务:
262
364
  def __init__(self, _client:DingTalkClient):
263
365
  self.__client:DingTalkClient = _client
264
- self.媒体文件 = 文档文件_媒体文件_API(_client)
265
- self.文件传输 = 文档文件_文件传输_API(_client)
266
366
 
267
- # noinspection NonAsciiCharacters
268
- class 文档文件_媒体文件_API:
367
+ def 创建钉钉待办任务(
368
+ self, owner_union_id:str, source_id:str, subject:str, description: str = None,
369
+ operator_union_id:str = None, due_time: datetime|int = None, is_only_show_executor = False,
370
+ executor_union_ids:List[str] = None, participant_union_ids:List[str] = None,
371
+ priority: Literal[10, 20, 30, 40] = None, ding_notify:bool = False,
372
+ app_url:str = None, pc_url:str = None
373
+ ) -> dict:
374
+
375
+ query = f"operatorId={operator_union_id}" if operator_union_id else ""
376
+
377
+ if isinstance(due_time, datetime):
378
+ due_time = int(due_time.timestamp() * 1000)
379
+
380
+ payload = {
381
+ "sourceId" : source_id,
382
+ "subject" : subject,
383
+ "creatorId" : owner_union_id,
384
+ "description" : description,
385
+ "dueTime" : due_time,
386
+ "executorIds" : executor_union_ids,
387
+ "participantIds" : participant_union_ids,
388
+ "detailUrl" : {
389
+ "appUrl" : app_url,
390
+ "pcUrl" : pc_url
391
+ },
392
+ "priority" : priority,
393
+ "isOnlyShowExecutor" : is_only_show_executor
394
+ }
395
+
396
+ if ding_notify:
397
+ payload["notifyConfigs"] = {"dingNotify" : "1"}
398
+
399
+ response = self.__client.api(
400
+ method="POST",
401
+ path=f"/v1.0/todo/users/{owner_union_id}/tasks?{query}",
402
+ json=payload
403
+ )
404
+ return response.json()
405
+
406
+ def 删除钉钉待办任务(
407
+ self, owner_union_id:str,task_id:str, operator_union_id:str = None
408
+ ) -> dict:
409
+
410
+ query = f"operatorId={operator_union_id}" if operator_union_id else ""
411
+
412
+ response = self.__client.api(
413
+ method="DELETE",
414
+ path=f"/v1.0/todo/users/{owner_union_id}/tasks/{task_id}?{query}"
415
+ )
416
+ return response.json()
417
+
418
+ def 更新钉钉待办任务(
419
+ self, owner_union_id:str, task_id:str, operator_union_id:str = None,
420
+ subject:str = None, description: str = None, due_time: datetime|int = None, done:bool = None,
421
+ executor_union_ids:List[str] = None, participant_union_ids:List[str] = None,
422
+ ) -> dict:
423
+
424
+ query = f"operatorId={operator_union_id}" if operator_union_id else ""
425
+
426
+ if isinstance(due_time, datetime):
427
+ due_time = int(due_time.timestamp() * 1000)
428
+
429
+ response = self.__client.api(
430
+ method="PUT",
431
+ path=f"/v1.0/todo/users/{owner_union_id}/tasks/{task_id}?{query}",
432
+ json={
433
+ "subject" : subject,
434
+ "description" : description,
435
+ "dueTime" : due_time,
436
+ "done" : done,
437
+ "executorIds" : executor_union_ids,
438
+ "participantIds" : participant_union_ids,
439
+ }
440
+ )
441
+ return response.json()
442
+
443
+ def 更新钉钉待办执行者状态(
444
+ self, owner_union_id:str, task_id:str, operator_union_id:str=None,
445
+ done_executor_union_ids:List[str] = None, not_done_executor_union_ids:List[str] = None,
446
+ ) -> dict:
447
+
448
+ executor_status_list = []
449
+
450
+ if done_executor_union_ids is not None:
451
+ executor_status_list.extend([{"id": union_id, "isDone": True} for union_id in done_executor_union_ids])
452
+ if not_done_executor_union_ids is not None:
453
+ executor_status_list.extend([{"id": union_id, "isDone": True} for union_id in not_done_executor_union_ids])
454
+
455
+ query = f"operatorId={operator_union_id}" if operator_union_id else ""
456
+
457
+ response = self.__client.api(
458
+ method="PUT",
459
+ path=f"/v1.0/todo/users/{owner_union_id}/tasks/{task_id}/executorStatus?{query}",
460
+ json={
461
+ "executorStatusList" : executor_status_list
462
+ }
463
+ )
464
+ return response.json()
465
+
466
+ def 查询企业下用户待办列表(
467
+ self, owner_union_id:str, next_token:str, is_down:bool, role_types:List[List[str]] = None
468
+ ) -> dict:
469
+
470
+ response = self.__client.api(
471
+ method="POST",
472
+ path=f"/v1.0/todo/users/{owner_union_id}/org/tasks/query",
473
+ json={
474
+ "nextToken" : next_token,
475
+ "isDone" : is_down,
476
+ "roleTypes": role_types
477
+ }
478
+ )
479
+ return response.json()
480
+
481
+
482
+ # 文档文件模块
483
+ # https://open.dingtalk.com/document/orgapp/knowledge-base-base-interface-permission-application
484
+ # todo 知识库
485
+ # todo 钉盘
486
+ # todo 群文件
487
+ # todo 文档
488
+
489
+
490
+ # noinspection NonAsciiCharacters, PyPep8Naming
491
+ class 文档文件__:
492
+ def __init__(self, _client:DingTalkClient):
493
+ self.__client:DingTalkClient = _client
494
+ self.媒体文件 = 文档文件__媒体文件(_client)
495
+ self.文件传输 = 文档文件__存储管理__文件传输(_client)
496
+
497
+
498
+ # noinspection NonAsciiCharacters, PyPep8Naming
499
+ class 文档文件__媒体文件:
269
500
  def __init__(self, _client:DingTalkClient):
270
501
  self.__client:DingTalkClient = _client
271
502
 
@@ -288,11 +519,14 @@ class 文档文件_媒体文件_API:
288
519
  }
289
520
  """
290
521
  with open(file_path, 'rb') as f:
291
- response = self.__client.oapi.post(url=f"/media/upload?type={media_type}", files={'media': f})
522
+ response = self.__client.oapi(
523
+ method="POST",
524
+ path=f"/media/upload?type={media_type}", files={'media': f})
292
525
  return response.json()
293
526
 
294
- # noinspection NonAsciiCharacters
295
- class 文档文件_文件传输_API:
527
+
528
+ # noinspection NonAsciiCharacters, PyPep8Naming
529
+ class 文档文件__存储管理__文件传输:
296
530
  def __init__(self, _client:DingTalkClient):
297
531
  self.__client:DingTalkClient = _client
298
532
 
@@ -319,8 +553,9 @@ class 文档文件_文件传输_API:
319
553
  }
320
554
  }
321
555
  """
322
- response = self.__client.api.post(
323
- url=f"/v1.0/storage/spaces/{space_id}/files/uploadInfos/query",
556
+ response = self.__client.api(
557
+ method="POST",
558
+ path=f"/v1.0/storage/spaces/{space_id}/files/uploadInfos/query",
324
559
  params={'unionId': union_id},
325
560
  json={
326
561
  "protocol": "HEADER_SIGNATURE",
@@ -359,9 +594,10 @@ class 文档文件_文件传输_API:
359
594
  headers=headers
360
595
  )
361
596
 
362
- response = self.__client.api.post(
363
- url = f"/v2.0/storage/spaces/files/{space_id}/commit?unionId={union_id}",
364
- json = {
597
+ response = self.__client.api(
598
+ method="POST",
599
+ path=f"/v2.0/storage/spaces/files/{space_id}/commit?unionId={union_id}",
600
+ json={
365
601
  "uploadKey": upload_key,
366
602
  "name": file_path.split("/")[-1],
367
603
  "convertToOnlineDoc": convert_to_online_doc
@@ -369,8 +605,9 @@ class 文档文件_文件传输_API:
369
605
  )
370
606
  return response.json()
371
607
 
372
- # noinspection NonAsciiCharacters
373
- class 互动卡片_API:
608
+
609
+ # noinspection NonAsciiCharacters, PyPep8Naming
610
+ class 互动卡片__:
374
611
  def __init__(self, _client:DingTalkClient):
375
612
  self.__client:DingTalkClient = _client
376
613
 
@@ -426,8 +663,9 @@ class 互动卡片_API:
426
663
  payload["topOpenSpaceModel"] = {"spaceType": "ONE_BOX"}
427
664
  payload["topOpenDeliverModel"] = {"platforms": ["android","ios","win","mac"], "expiredTimeMillis": expired_time_millis,}
428
665
 
429
- response = self.__client.api.post(
430
- url="/v1.0/card/instances/createAndDeliver",
666
+ response = self.__client.api(
667
+ method="POST",
668
+ path="/v1.0/card/instances/createAndDeliver",
431
669
  json=payload
432
670
  )
433
671
 
@@ -446,8 +684,9 @@ class 互动卡片_API:
446
684
  {success: bool, result: bool}
447
685
  """
448
686
 
449
- response = self.__client.api.put(
450
- url="/v1.0/card/instances",
687
+ response = self.__client.api(
688
+ method="PUT",
689
+ path="/v1.0/card/instances",
451
690
  json={
452
691
  "outTrackId": out_track_id,
453
692
  "cardData": {"cardParamMap": card_param_map},
@@ -457,15 +696,17 @@ class 互动卡片_API:
457
696
 
458
697
  return response.json()
459
698
 
460
- # noinspection NonAsciiCharacters
461
- class OA审批_API:
699
+
700
+ # noinspection NonAsciiCharacters, PyPep8Naming
701
+ class OA审批__:
462
702
  def __init__(self, _client:DingTalkClient):
463
703
  self.__client:DingTalkClient = _client
464
- self.审批实例 = OA审批_审批实例_API(_client)
465
- self.审批钉盘 = OA审批_审批钉盘_API(_client)
704
+ self.审批实例 = OA审批_审批实例__(_client)
705
+ self.审批钉盘 = OA审批_审批钉盘__(_client)
466
706
 
467
- # noinspection NonAsciiCharacters
468
- class OA审批_审批实例_API:
707
+
708
+ # noinspection NonAsciiCharacters, PyPep8Naming
709
+ class OA审批_审批实例__:
469
710
  def __init__(self, _client:DingTalkClient):
470
711
  self.__client:DingTalkClient = _client
471
712
 
@@ -490,7 +731,9 @@ class OA审批_审批实例_API:
490
731
  "result": {}
491
732
  }
492
733
  """
493
- response = self.__client.api.get(url="/v1.0/workflow/processInstances", params={'processInstanceId': instance_id})
734
+ response = self.__client.api(
735
+ method="GET",
736
+ path="/v1.0/workflow/processInstances", params={'processInstanceId': instance_id})
494
737
  return response.json()
495
738
 
496
739
  def 撤销审批实例(self, instance_id:str, is_system:bool = True, remark:str|None = None, operating_user_id:str = None) -> dict:
@@ -510,8 +753,9 @@ class OA审批_审批实例_API:
510
753
  "result": {}
511
754
  }
512
755
  """
513
- response = self.__client.api.post(
514
- url="/v1.0/workflow/processInstances",
756
+ response = self.__client.api(
757
+ method="POST",
758
+ path="/v1.0/workflow/processInstances",
515
759
  json={
516
760
  "processInstanceId" : instance_id,
517
761
  "isSystem" : is_system,
@@ -554,8 +798,9 @@ class OA审批_审批实例_API:
554
798
  if photos or attachments:
555
799
  data.update({'file': {"photos": photos, "attachments": attachments}})
556
800
 
557
- response = self.__client.api.post(
558
- url="/v1.0/workflow/processInstances/comments",
801
+ response = self.__client.api(
802
+ method="POST",
803
+ path="/v1.0/workflow/processInstances/comments",
559
804
  json=data
560
805
  )
561
806
  return response.json()
@@ -583,8 +828,9 @@ class OA审批_审批实例_API:
583
828
  "result": {}
584
829
  }
585
830
  """
586
- response = self.__client.api.post(
587
- url="/v1.0/workflow/processes/instanceIds/query",
831
+ response = self.__client.api(
832
+ method="POST",
833
+ path="/v1.0/workflow/processes/instanceIds/query",
588
834
  json={
589
835
  "processCode" : process_code,
590
836
  "startTime" : int(start_time.timestamp()*1000),
@@ -596,8 +842,9 @@ class OA审批_审批实例_API:
596
842
  })
597
843
  return response.json()
598
844
 
599
- # noinspection NonAsciiCharacters
600
- class OA审批_审批钉盘_API:
845
+
846
+ # noinspection NonAsciiCharacters, PyPep8Naming
847
+ class OA审批_审批钉盘__:
601
848
  def __init__(self, _client:DingTalkClient):
602
849
  self.__client:DingTalkClient = _client
603
850
 
@@ -617,22 +864,25 @@ class OA审批_审批钉盘_API:
617
864
  }
618
865
  }
619
866
  """
620
- response = self.__client.api.post(
621
- url="/v1.0/workflow/processInstances/spaces/infos/query",
867
+ response = self.__client.api(
868
+ method="POST",
869
+ path="/v1.0/workflow/processInstances/spaces/infos/query",
622
870
  json={
623
871
  "userId" : user_id,
624
872
  "agentId" : self.__client.agent_id
625
873
  })
626
874
  return response.json()
627
875
 
628
- # noinspection NonAsciiCharacters
629
- class 即时通信_API:
876
+
877
+ # noinspection NonAsciiCharacters, PyPep8Naming
878
+ class 即时通信__:
630
879
  def __init__(self, _client:DingTalkClient):
631
880
  self.__client:DingTalkClient = _client
632
- self.工作通知 = 即时通信_工作通知_API(_client)
881
+ self.工作通知 = 即时通信_工作通知__(_client)
633
882
 
634
- # noinspection NonAsciiCharacters
635
- class 即时通信_工作通知_API:
883
+
884
+ # noinspection NonAsciiCharacters, PyPep8Naming
885
+ class 即时通信_工作通知__:
636
886
  def __init__(self, _client:DingTalkClient):
637
887
  self.__client:DingTalkClient = _client
638
888
 
@@ -651,8 +901,9 @@ class 即时通信_工作通知_API:
651
901
  :param dept_id_list: 接收者的部门 id 列表,最大列表长度 20 。接收者是部门 ID 时,包括子部门下的所有用户。
652
902
  :return:
653
903
  """
654
- response = self.__client.oapi.post(
655
- url="/topapi/message/corpconversation/asyncsend_v2",
904
+ response = self.__client.oapi(
905
+ method="POST",
906
+ path="/topapi/message/corpconversation/asyncsend_v2",
656
907
  json={
657
908
  "agent_id" : self.__client.agent_id,
658
909
  "to_all_user": to_all_user,