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.
- dagster_dingtalk/__init__.py +3 -3
- dagster_dingtalk/app_client.py +399 -148
- dagster_dingtalk/resources.py +422 -422
- dagster_dingtalk/version.py +1 -1
- {dagster_dingtalk-0.1.23.dist-info → dagster_dingtalk-0.1.26.dist-info}/METADATA +10 -15
- dagster_dingtalk-0.1.26.dist-info/RECORD +8 -0
- {dagster_dingtalk-0.1.23.dist-info → dagster_dingtalk-0.1.26.dist-info}/WHEEL +1 -1
- dagster_dingtalk-0.1.23.dist-info/RECORD +0 -8
dagster_dingtalk/app_client.py
CHANGED
@@ -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__(
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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(
|
48
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
84
|
-
|
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.花名册 = 智能人事
|
88
|
-
self.员工管理 = 智能人事
|
154
|
+
self.花名册 = 智能人事__花名册(_client)
|
155
|
+
self.员工管理 = 智能人事__员工管理(_client)
|
89
156
|
|
90
|
-
|
91
|
-
|
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
|
97
|
-
|
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
|
104
|
-
|
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
|
117
|
-
|
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
|
-
|
134
|
-
|
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
|
150
|
-
"
|
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
|
157
|
-
"
|
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
|
164
|
-
"
|
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
|
171
|
-
"
|
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
|
-
|
177
|
-
|
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.用户管理 = 通讯录管理
|
181
|
-
self.部门管理 = 通讯录管理
|
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
|
-
|
187
|
-
|
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
|
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
|
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
|
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
|
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 通讯录管理
|
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
|
226
|
-
|
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
|
241
|
-
|
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
|
255
|
-
|
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
|
-
|
261
|
-
|
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
|
-
|
268
|
-
|
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
|
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
|
-
|
295
|
-
|
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
|
323
|
-
|
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
|
363
|
-
|
364
|
-
|
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
|
-
|
373
|
-
|
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
|
430
|
-
|
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
|
450
|
-
|
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
|
-
|
461
|
-
|
699
|
+
|
700
|
+
# noinspection NonAsciiCharacters, PyPep8Naming
|
701
|
+
class OA审批__:
|
462
702
|
def __init__(self, _client:DingTalkClient):
|
463
703
|
self.__client:DingTalkClient = _client
|
464
|
-
self.审批实例 = OA审批_审批实例
|
465
|
-
self.审批钉盘 = OA审批_审批钉盘
|
704
|
+
self.审批实例 = OA审批_审批实例__(_client)
|
705
|
+
self.审批钉盘 = OA审批_审批钉盘__(_client)
|
466
706
|
|
467
|
-
|
468
|
-
|
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
|
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
|
514
|
-
|
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
|
558
|
-
|
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
|
587
|
-
|
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
|
-
|
600
|
-
|
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
|
621
|
-
|
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
|
-
|
629
|
-
|
876
|
+
|
877
|
+
# noinspection NonAsciiCharacters, PyPep8Naming
|
878
|
+
class 即时通信__:
|
630
879
|
def __init__(self, _client:DingTalkClient):
|
631
880
|
self.__client:DingTalkClient = _client
|
632
|
-
self.工作通知 = 即时通信_工作通知
|
881
|
+
self.工作通知 = 即时通信_工作通知__(_client)
|
633
882
|
|
634
|
-
|
635
|
-
|
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
|
655
|
-
|
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,
|