dagster-dingtalk 0.1.10b1__py3-none-any.whl → 0.1.11__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 +1 -0
- dagster_dingtalk/app_client.py +198 -19
- dagster_dingtalk/resources.py +259 -67
- dagster_dingtalk/version.py +1 -1
- {dagster_dingtalk-0.1.10b1.dist-info → dagster_dingtalk-0.1.11.dist-info}/METADATA +1 -1
- dagster_dingtalk-0.1.11.dist-info/RECORD +8 -0
- dagster_dingtalk-0.1.10b1.dist-info/RECORD +0 -8
- {dagster_dingtalk-0.1.10b1.dist-info → dagster_dingtalk-0.1.11.dist-info}/WHEEL +0 -0
    
        dagster_dingtalk/__init__.py
    CHANGED
    
    | @@ -3,6 +3,7 @@ from dagster._core.libraries import DagsterLibraryRegistry | |
| 3 3 |  | 
| 4 4 | 
             
            from dagster_dingtalk.resources import DingTalkAppResource
         | 
| 5 5 | 
             
            from dagster_dingtalk.resources import DingTalkWebhookResource
         | 
| 6 | 
            +
            from dagster_dingtalk.app_client import DingTalkClient as DingTalkAppClient
         | 
| 6 7 | 
             
            # from dagster_dingtalk.operations import DingTalkWebhookOp
         | 
| 7 8 | 
             
            from dagster_dingtalk.version import __version__
         | 
| 8 9 |  | 
    
        dagster_dingtalk/app_client.py
    CHANGED
    
    | @@ -15,48 +15,77 @@ class DingTalkClient: | |
| 15 15 | 
             
                    self.app_name: str|None = app_name
         | 
| 16 16 | 
             
                    self.agent_id: int|None = agent_id
         | 
| 17 17 | 
             
                    self.client_id: str = client_id
         | 
| 18 | 
            -
                    self. | 
| 18 | 
            +
                    self.__client_secret: str = client_secret
         | 
| 19 19 | 
             
                    self.robot_code: str = client_id
         | 
| 20 20 |  | 
| 21 | 
            -
                    access_token: str = self. | 
| 21 | 
            +
                    access_token: str = self.__get_access_token()
         | 
| 22 22 | 
             
                    self.api: Client = Client(base_url="https://api.dingtalk.com/", headers={"x-acs-dingtalk-access-token": access_token})
         | 
| 23 23 | 
             
                    self.oapi: Client = Client(base_url="https://oapi.dingtalk.com/", params={"access_token": access_token})
         | 
| 24 24 |  | 
| 25 25 | 
             
                    self.智能人事 = 智能人事_API(self)
         | 
| 26 26 | 
             
                    self.通讯录管理 = 通讯录管理_API(self)
         | 
| 27 27 | 
             
                    self.文档文件 = 文档文件_API(self)
         | 
| 28 | 
            +
                    self.互动卡片 = 互动卡片_API(self)
         | 
| 28 29 |  | 
| 29 | 
            -
                def  | 
| 30 | 
            +
                def __get_access_token(self) -> str:
         | 
| 30 31 | 
             
                    access_token_cache = Path("/tmp/.dingtalk_cache")
         | 
| 32 | 
            +
                    all_access_token: dict = {}
         | 
| 33 | 
            +
                    access_token: str|None = None
         | 
| 34 | 
            +
                    expire_in: int = 0
         | 
| 35 | 
            +
                    renew_reason = None
         | 
| 31 36 |  | 
| 32 | 
            -
                     | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
                         | 
| 37 | 
            +
                    # 从缓存中读取
         | 
| 38 | 
            +
                    if not access_token_cache.exists():
         | 
| 39 | 
            +
                        renew_reason = f"鉴权缓存不存在"
         | 
| 40 | 
            +
                    else:
         | 
| 41 | 
            +
                        try:
         | 
| 42 | 
            +
                            with open(access_token_cache, 'rb') as f:
         | 
| 43 | 
            +
                                cache_file = f.read()
         | 
| 44 | 
            +
                        except Exception as e:
         | 
| 45 | 
            +
                            logging.error(e)
         | 
| 46 | 
            +
                            cache_file = None
         | 
| 47 | 
            +
                            renew_reason = "鉴权缓存读取错误"
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    if cache_file:
         | 
| 50 | 
            +
                        try:
         | 
| 51 | 
            +
                            all_access_token = pickle.loads(cache_file)
         | 
| 52 | 
            +
                        except pickle.PickleError:
         | 
| 53 | 
            +
                            renew_reason = f"鉴权缓存解析错误"
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    if all_access_token:
         | 
| 56 | 
            +
                        app_access_token = all_access_token.get(self.app_id)
         | 
| 57 | 
            +
                        access_token = app_access_token.get('access_token')
         | 
| 58 | 
            +
                        expire_in = app_access_token.get('expire_in')
         | 
| 59 | 
            +
                    else:
         | 
| 60 | 
            +
                        renew_reason = f"鉴权缓存不存在该应用 {self.app_name}<{self.app_id}>"
         | 
| 40 61 |  | 
| 41 | 
            -
                    if access_token | 
| 62 | 
            +
                    if not access_token:
         | 
| 63 | 
            +
                        renew_reason = F"应用 {self.app_name}<{self.app_id}> 的鉴权缓存无效"
         | 
| 64 | 
            +
                    if expire_in < int(time.time()):
         | 
| 65 | 
            +
                        renew_reason = F"应用 {self.app_name}<{self.app_id}> 的鉴权缓存过期"
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    if renew_reason is None:
         | 
| 42 68 | 
             
                        return access_token
         | 
| 43 69 | 
             
                    else:
         | 
| 44 | 
            -
                        logging.warning( | 
| 70 | 
            +
                        logging.warning(renew_reason)
         | 
| 45 71 | 
             
                        response = Client().post(
         | 
| 46 72 | 
             
                            url="https://api.dingtalk.com/v1.0/oauth2/accessToken",
         | 
| 47 | 
            -
                            json={"appKey": self.client_id, "appSecret": self. | 
| 73 | 
            +
                            json={"appKey": self.client_id, "appSecret": self.__client_secret},
         | 
| 48 74 | 
             
                        )
         | 
| 49 75 | 
             
                        access_token:str = response.json().get("accessToken")
         | 
| 50 76 | 
             
                        expire_in:int = response.json().get("expireIn") + int(time.time()) - 60
         | 
| 51 77 | 
             
                        with open(access_token_cache, 'wb') as f:
         | 
| 52 | 
            -
                            all_access_token[self.app_id] =  | 
| 78 | 
            +
                            all_access_token[self.app_id] = {
         | 
| 79 | 
            +
                                "access_token": access_token,
         | 
| 80 | 
            +
                                "expire_in": expire_in,
         | 
| 81 | 
            +
                            }
         | 
| 53 82 | 
             
                            f.write(pickle.dumps(all_access_token))
         | 
| 54 83 | 
             
                        return access_token
         | 
| 55 84 |  | 
| 56 | 
            -
             | 
| 57 85 | 
             
            # noinspection NonAsciiCharacters
         | 
| 58 86 | 
             
            class 智能人事_API:
         | 
| 59 87 | 
             
                def __init__(self, _client:DingTalkClient):
         | 
| 88 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 60 89 | 
             
                    self.花名册 = 智能人事_花名册_API(_client)
         | 
| 61 90 | 
             
                    self.员工管理 = 智能人事_员工管理_API(_client)
         | 
| 62 91 |  | 
| @@ -84,7 +113,6 @@ class 智能人事_花名册_API: | |
| 84 113 |  | 
| 85 114 | 
             
            # noinspection NonAsciiCharacters
         | 
| 86 115 | 
             
            class 智能人事_员工管理_API:
         | 
| 87 | 
            -
             | 
| 88 116 | 
             
                def __init__(self, _client:DingTalkClient):
         | 
| 89 117 | 
             
                    self.__client:DingTalkClient = _client
         | 
| 90 118 |  | 
| @@ -126,7 +154,17 @@ class 智能人事_员工管理_API: | |
| 126 154 | 
             
            # noinspection NonAsciiCharacters
         | 
| 127 155 | 
             
            class 通讯录管理_API:
         | 
| 128 156 | 
             
                def __init__(self, _client:DingTalkClient):
         | 
| 129 | 
            -
                    self.__client = _client
         | 
| 157 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 158 | 
            +
                    self.用户管理 = 通讯录管理_用户管理_API(_client)
         | 
| 159 | 
            +
                    self.部门管理 = 通讯录管理_部门管理_API(_client)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                def 查询用户详情(self, user_id:str, language:str = "zh_CN"):
         | 
| 162 | 
            +
                    return self.用户管理.查询用户详情(user_id, language)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
            # noinspection NonAsciiCharacters
         | 
| 165 | 
            +
            class 通讯录管理_用户管理_API:
         | 
| 166 | 
            +
                def __init__(self, _client:DingTalkClient):
         | 
| 167 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 130 168 |  | 
| 131 169 | 
             
                def 查询用户详情(self, user_id:str, language:str = "zh_CN") -> dict:
         | 
| 132 170 | 
             
                    response = self.__client.oapi.post(url="/topapi/v2/user/get", json={"language": language, "userid": user_id})
         | 
| @@ -139,17 +177,158 @@ class 通讯录管理_API: | |
| 139 177 | 
             
                    response = self.__client.api.get(url="/v1.0/contact/empLeaveRecords", params=params)
         | 
| 140 178 | 
             
                    return response.json()
         | 
| 141 179 |  | 
| 180 | 
            +
            # noinspection NonAsciiCharacters
         | 
| 181 | 
            +
            class 通讯录管理_部门管理_API:
         | 
| 182 | 
            +
                def __init__(self, _client:DingTalkClient):
         | 
| 183 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                def 获取部门详情(self, dept_id: int, language:str = "zh_CN") -> dict:
         | 
| 186 | 
            +
                    """
         | 
| 187 | 
            +
                    调用本接口,根据部门ID获取指定部门详情。
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    https://open.dingtalk.com/document/orgapp/query-department-details0-v2
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                    :param dept_id: 部门 ID ,根部门 ID 为 1。
         | 
| 192 | 
            +
                    :param language: 通讯录语言。zh_CN en_US
         | 
| 193 | 
            +
                    """
         | 
| 194 | 
            +
                    response = self.__client.oapi.post(
         | 
| 195 | 
            +
                        url="/topapi/v2/department/get",
         | 
| 196 | 
            +
                        json={"language": language, "dept_id": dept_id}
         | 
| 197 | 
            +
                    )
         | 
| 198 | 
            +
                    return response.json()
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                def 获取部门列表(self, dept_id: int, language:str = "zh_CN"):
         | 
| 201 | 
            +
                    """
         | 
| 202 | 
            +
                    调用本接口,获取下一级部门基础信息。
         | 
| 203 | 
            +
             | 
| 204 | 
            +
                    https://open.dingtalk.com/document/orgapp/obtain-the-department-list-v2
         | 
| 205 | 
            +
             | 
| 206 | 
            +
                    :param dept_id: 部门 ID ,根部门 ID 为 1。
         | 
| 207 | 
            +
                    :param language: 通讯录语言。zh_CN en_US
         | 
| 208 | 
            +
                    """
         | 
| 209 | 
            +
                    response = self.__client.oapi.post(
         | 
| 210 | 
            +
                        url="/topapi/v2/department/listsub",
         | 
| 211 | 
            +
                        json={"language": language, "dept_id": dept_id}
         | 
| 212 | 
            +
                    )
         | 
| 213 | 
            +
                    return response.json()
         | 
| 214 | 
            +
             | 
| 142 215 | 
             
            # noinspection NonAsciiCharacters
         | 
| 143 216 | 
             
            class 文档文件_API:
         | 
| 144 217 | 
             
                def __init__(self, _client:DingTalkClient):
         | 
| 218 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 145 219 | 
             
                    self.媒体文件 = 文档文件_媒体文件_API(_client)
         | 
| 146 220 |  | 
| 147 221 | 
             
            # noinspection NonAsciiCharacters
         | 
| 148 222 | 
             
            class 文档文件_媒体文件_API:
         | 
| 149 223 | 
             
                def __init__(self, _client:DingTalkClient):
         | 
| 150 | 
            -
                    self.__client = _client
         | 
| 224 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 151 225 |  | 
| 152 226 | 
             
                def 上传媒体文件(self, file_path:Path|str, media_type:Literal['image', 'voice', 'video', 'file']) -> dict:
         | 
| 227 | 
            +
                    """
         | 
| 228 | 
            +
                    调用本接口,上传图片、语音媒体资源文件以及普通文件,接口返回媒体资源标识 media_id。
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                    https://open.dingtalk.com/document/orgapp/upload-media-files
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    :param file_path: 本地文件路径
         | 
| 233 | 
            +
                    :param media_type: 媒体类型,支持 'image', 'voice', 'video', 'file'
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                    :return:
         | 
| 236 | 
            +
                        {
         | 
| 237 | 
            +
                            "errcode": 0,
         | 
| 238 | 
            +
                            "errmsg": "ok",
         | 
| 239 | 
            +
                            "media_id": "$iAEKAqDBgTNAk",
         | 
| 240 | 
            +
                            "created_at": 1605863153573,
         | 
| 241 | 
            +
                            "type": "image"
         | 
| 242 | 
            +
                        }
         | 
| 243 | 
            +
                    """
         | 
| 153 244 | 
             
                    with open(file_path, 'rb') as f:
         | 
| 154 245 | 
             
                        response = self.__client.oapi.post(url=f"/media/upload?type={media_type}", files={'media': f})
         | 
| 155 246 | 
             
                    return response.json()
         | 
| 247 | 
            +
             | 
| 248 | 
            +
            # noinspection NonAsciiCharacters
         | 
| 249 | 
            +
            class 互动卡片_API:
         | 
| 250 | 
            +
                def __init__(self, _client:DingTalkClient):
         | 
| 251 | 
            +
                    self.__client:DingTalkClient = _client
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                def 创建并投放卡片(
         | 
| 254 | 
            +
                        self, search_type_name: str, search_desc: str, card_template_id: str, card_param_map: dict,
         | 
| 255 | 
            +
                        alert_content: str, open_space_ids: List[str], out_track_id: str, support_forward: bool = True,
         | 
| 256 | 
            +
                        call_back_type: str = "STREAM", expired_time_millis:int = 0
         | 
| 257 | 
            +
                ) -> dict:
         | 
| 258 | 
            +
                    """
         | 
| 259 | 
            +
                    创建并投放卡片。当前仅支持 IM群聊, IM机器人单聊, 吊顶 三种场域类型。
         | 
| 260 | 
            +
             | 
| 261 | 
            +
                    https://open.dingtalk.com/document/orgapp/create-and-deliver-cards
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                    :param card_template_id: 卡片模板 ID
         | 
| 264 | 
            +
                    :param open_space_ids: 卡片投放场域 Id
         | 
| 265 | 
            +
                    :param out_track_id: 卡片唯一标识
         | 
| 266 | 
            +
                    :param card_param_map: 卡片数据
         | 
| 267 | 
            +
                    :param search_type_name: 卡片类型名
         | 
| 268 | 
            +
                    :param search_desc: 卡片消息展示
         | 
| 269 | 
            +
                    :param alert_content: 通知内容
         | 
| 270 | 
            +
                    :param call_back_type: 回调模式
         | 
| 271 | 
            +
                    :param support_forward: 是否支持转发
         | 
| 272 | 
            +
                    :param expired_time_millis: 吊顶投放过期时间。当投放内容为吊顶时必须传参。
         | 
| 273 | 
            +
                    """
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                    open_space_id = f"dtv1.card//{';'.join(open_space_ids)}"
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                    payload = {
         | 
| 278 | 
            +
                        "cardTemplateId": card_template_id,
         | 
| 279 | 
            +
                        "outTrackId": out_track_id,
         | 
| 280 | 
            +
                        "openSpaceId": open_space_id,
         | 
| 281 | 
            +
                        "callbackType": call_back_type,
         | 
| 282 | 
            +
                        "cardData": {"cardParamMap": card_param_map}
         | 
| 283 | 
            +
                    }
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                    open_space_model = {
         | 
| 286 | 
            +
                            "supportForward": support_forward,
         | 
| 287 | 
            +
                            "searchSupport": {"searchTypeName": search_type_name, "searchDesc": search_desc},
         | 
| 288 | 
            +
                            "notification": {"alertContent": alert_content, "notificationOff": False}
         | 
| 289 | 
            +
                        }
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                    if 'IM_GROUP' in open_space_id.upper():
         | 
| 292 | 
            +
                        payload["imGroupOpenSpaceModel"] = open_space_model
         | 
| 293 | 
            +
                        payload["imGroupOpenDeliverModel"] = {"robotCode": self.__client.robot_code}
         | 
| 294 | 
            +
             | 
| 295 | 
            +
                    if 'IM_ROBOT' in open_space_id.upper():
         | 
| 296 | 
            +
                        payload["imRobotOpenSpaceModel"] = open_space_model
         | 
| 297 | 
            +
                        payload["imRobotOpenDeliverModel"] = {"spaceType": "IM_ROBOT", "robotCode": self.__client.robot_code}
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                    if 'ONE_BOX' in open_space_id.upper():
         | 
| 300 | 
            +
                        if expired_time_millis == 0:
         | 
| 301 | 
            +
                            expired_time_millis = int(time.time()+3600)*1000
         | 
| 302 | 
            +
                        payload["topOpenSpaceModel"] = {"spaceType": "ONE_BOX"}
         | 
| 303 | 
            +
                        payload["topOpenDeliverModel"] = {"platforms": ["android","ios","win","mac"], "expiredTimeMillis": expired_time_millis,}
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                    response = self.__client.api.post(
         | 
| 306 | 
            +
                        url="/v1.0/card/instances/createAndDeliver",
         | 
| 307 | 
            +
                        json=payload
         | 
| 308 | 
            +
                    )
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                    return response.json()
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                def 更新卡片(self, out_track_id: str, card_param_map: dict, update_card_data_by_key:bool=True) -> dict:
         | 
| 313 | 
            +
                    """
         | 
| 314 | 
            +
                    调用本接口,实现主动更新卡片数据。
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                    https://open.dingtalk.com/document/orgapp/interactive-card-update-interface
         | 
| 317 | 
            +
             | 
| 318 | 
            +
                    :param out_track_id: 外部卡片实例Id。
         | 
| 319 | 
            +
                    :param card_param_map: 卡片模板内容。
         | 
| 320 | 
            +
                    :param update_card_data_by_key: True-按 key 更新 cardData 数据 False-覆盖更新 cardData 数据
         | 
| 321 | 
            +
                    :return:
         | 
| 322 | 
            +
                        {success: bool, result: bool}
         | 
| 323 | 
            +
                    """
         | 
| 324 | 
            +
             | 
| 325 | 
            +
                    response = self.__client.api.put(
         | 
| 326 | 
            +
                        url="/v1.0/card/instances",
         | 
| 327 | 
            +
                        json={
         | 
| 328 | 
            +
                            "outTrackId": out_track_id,
         | 
| 329 | 
            +
                            "cardData": {"cardParamMap": card_param_map},
         | 
| 330 | 
            +
                            "cardUpdateOptions": {"updateCardDataByKey": update_card_data_by_key}
         | 
| 331 | 
            +
                        }
         | 
| 332 | 
            +
                    )
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                    return response.json()
         | 
    
        dagster_dingtalk/resources.py
    CHANGED
    
    | @@ -7,27 +7,123 @@ import urllib.parse | |
| 7 7 | 
             
            from typing import Optional, Tuple, List, Literal
         | 
| 8 8 | 
             
            import httpx
         | 
| 9 9 | 
             
            from pydantic import Field
         | 
| 10 | 
            -
            from app_client import DingTalkClient
         | 
| 10 | 
            +
            from .app_client import DingTalkClient
         | 
| 11 11 | 
             
            from dagster import ConfigurableResource, InitResourceContext
         | 
| 12 12 |  | 
| 13 13 |  | 
| 14 | 
            +
            DINGTALK_WEBHOOK_EXCEPTION_SOLUTIONS = {
         | 
| 15 | 
            +
                "-1": "\n系统繁忙,请稍后重试",
         | 
| 16 | 
            +
                "40035": "\n缺少参数 json,请补充消息json",
         | 
| 17 | 
            +
                "43004": "\n无效的HTTP HEADER Content-Type,请设置具体的消息参数",
         | 
| 18 | 
            +
                "400013": "\n群已被解散,请向其他群发消息",
         | 
| 19 | 
            +
                "400101": "\naccess_token不存在,请确认access_token拼写是否正确",
         | 
| 20 | 
            +
                "400102": "\n机器人已停用,请联系管理员启用机器人",
         | 
| 21 | 
            +
                "400105": "\n不支持的消息类型,请使用文档中支持的消息类型",
         | 
| 22 | 
            +
                "400106": "\n机器人不存在,请确认机器人是否在群中",
         | 
| 23 | 
            +
                "410100": "\n发送速度太快而限流,请降低发送速度",
         | 
| 24 | 
            +
                "430101": "\n含有不安全的外链,请确认发送的内容合法",
         | 
| 25 | 
            +
                "430102": "\n含有不合适的文本,请确认发送的内容合法",
         | 
| 26 | 
            +
                "430103": "\n含有不合适的图片,请确认发送的内容合法",
         | 
| 27 | 
            +
                "430104": "\n含有不合适的内容,请确认发送的内容合法",
         | 
| 28 | 
            +
                "310000": "\n消息校验未通过,请查看机器人的安全设置",
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
             | 
| 32 | 
            +
            class DingTalkWebhookException(Exception):
         | 
| 33 | 
            +
                def __init__(self, errcode, errmsg):
         | 
| 34 | 
            +
                    self.errcode = errcode
         | 
| 35 | 
            +
                    self.errmsg = errmsg
         | 
| 36 | 
            +
                    super().__init__(f"DingTalkWebhookError {errcode}: {errmsg} {DINGTALK_WEBHOOK_EXCEPTION_SOLUTIONS.get(errcode)}")
         | 
| 37 | 
            +
             | 
| 38 | 
            +
             | 
| 14 39 | 
             
            class DingTalkWebhookResource(ConfigurableResource):
         | 
| 15 40 | 
             
                """
         | 
| 16 | 
            -
                 | 
| 41 | 
            +
                该资源允许定义单个钉钉自定义机器人的 Webhook 端点,以便于发送文本、Markdown、Link、 ActionCard、FeedCard 消息,消息具体样式可参考
         | 
| 42 | 
            +
                [钉钉开放平台 | 自定义机器人发送消息的消息类型](https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type)。
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                ### 配置项:
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                - **access_token** (str):
         | 
| 47 | 
            +
                    机器人 Webhook 地址中的 access_token 值。
         | 
| 48 | 
            +
                - **secret** (str, optional):
         | 
| 49 | 
            +
                    如使用加签安全配置,则需传签名密钥。默认值为 None。
         | 
| 50 | 
            +
                - **alias** (str, optional):
         | 
| 51 | 
            +
                    如提供别名,可以在使用 `MultiDingTalkWebhookResource` 中使用别名进行 webhook 选择。默认值为 None。
         | 
| 52 | 
            +
                - **base_url** (str, optional):
         | 
| 53 | 
            +
                    通用地址,一般无需更改。默认值为 “https://oapi.dingtalk.com/robot/send”。
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                ### 用例:
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                1. 使用单个资源:
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                ```python
         | 
| 60 | 
            +
                from dagster_dingtalk import DingTalkWebhookResource
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                @op(required_resource_keys={"dingtalk_webhook"}, ins={"text": In(str)})
         | 
| 63 | 
            +
                def op_send_text(context:OpExecutionContext, text:str):
         | 
| 64 | 
            +
                    dingtalk_webhook:DingTalkWebhookResource = context.resources.dingtalk_webhook
         | 
| 65 | 
            +
                    result = dingtalk.send_text(text)
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                @job
         | 
| 68 | 
            +
                def job_send_text():
         | 
| 69 | 
            +
                    op_send_text
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                defs = Definitions(
         | 
| 72 | 
            +
                    jobs=job_user_info,
         | 
| 73 | 
            +
                    resources={"dingtalk_webhook": DingTalkWebhookResource(access_token = "<access_token>", secret = "<secret>")}
         | 
| 74 | 
            +
                )
         | 
| 75 | 
            +
                ```
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                2. 启动时动态构建企业内部应用资源, 可参考 [Dagster文档 | 在启动时配置资源](https://docs.dagster.io/concepts/resources#configuring-resources-at-launch-time)
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                ```python
         | 
| 80 | 
            +
                from dagster_dingtalk import DingTalkWebhookResource
         | 
| 17 81 |  | 
| 18 | 
            -
                 | 
| 19 | 
            -
                 | 
| 82 | 
            +
                @op(required_resource_keys={"dingtalk_webhook"}, ins={"text": In(str)})
         | 
| 83 | 
            +
                def op_send_text(context:OpExecutionContext, text:str):
         | 
| 84 | 
            +
                    dingtalk_webhook:DingTalkWebhookResource = context.resources.dingtalk_webhook
         | 
| 85 | 
            +
                    result = dingtalk.send_text(text)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                @job
         | 
| 88 | 
            +
                def job_send_text():
         | 
| 89 | 
            +
                    op_send_text
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                dingtalk_webhooks = {
         | 
| 92 | 
            +
                    "Group1" : DingTalkWebhookResource(access_token="<access_token>", secret="<secret>", alias="Group1"),
         | 
| 93 | 
            +
                    "Group2" : DingTalkWebhookResource(access_token="<access_token>", secret="<secret>", alias="Group2")
         | 
| 94 | 
            +
                }
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                defs = Definitions(jobs=job_send_text, resources={"dingtalk_webhook": DingTalkWebhookResource.configure_at_launch()})
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                @schedule(cron_schedule="20 9 * * *", job=job_send_text)
         | 
| 99 | 
            +
                def schedule_user_info():
         | 
| 100 | 
            +
                    return RunRequest(run_config=RunConfig(
         | 
| 101 | 
            +
                        ops={"op_send_text": {"inputs": {"text": "This a test text."}}},
         | 
| 102 | 
            +
                        resources={"dingtalk": dingtalk_webhooks["Group1"]},
         | 
| 103 | 
            +
                    ))
         | 
| 104 | 
            +
                ```
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                ### 注意:
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                应该永远避免直接将密钥字符串直接配置给资源,这会导致在 dagster 前端用户界面暴露密钥。
         | 
| 109 | 
            +
                应当从环境变量中读取密钥。你可以在代码中注册临时的环境变量,或从系统中引入环境变量。
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                ```python
         | 
| 112 | 
            +
                import os
         | 
| 113 | 
            +
                from dagster_dingtalk import DingTalkWebhookResource
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                # 直接在代码中注册临时的环境变量
         | 
| 116 | 
            +
                os.environ.update({'access_token': "<access_token>"})
         | 
| 117 | 
            +
                os.environ.update({'secret': "<secret>"})
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                webhook = DingTalkWebhookResource(access_token=EnvVar("access_token"), secret=EnvVar("secret"))
         | 
| 20 120 |  | 
| 21 | 
            -
                Args:
         | 
| 22 | 
            -
                    access_token (str): 机器人 Webhook 地址中的 access_token 值。
         | 
| 23 | 
            -
                    secret (str, optional): 如使用加签安全配置,则需传签名密钥。默认值为 None。
         | 
| 24 | 
            -
                    alias (str, optional): 如提供别名,可以在使用 `MultiDingTalkWebhookResource` 中使用别名进行 webhook 选择。默认值为 None。
         | 
| 25 | 
            -
                    base_url (str, optional): 通用地址,一般无需更改。默认值为 “https://oapi.dingtalk.com/robot/send”。
         | 
| 26 121 |  | 
| 27 122 | 
             
                """
         | 
| 123 | 
            +
             | 
| 28 124 | 
             
                access_token: str = Field(description="Webhook地址中的 access_token 部分")
         | 
| 29 125 | 
             
                secret: Optional[str] = Field(default=None, description="如使用加签安全配置,需传签名密钥")
         | 
| 30 | 
            -
                alias: Optional[str] = Field(default=None, description=" | 
| 126 | 
            +
                alias: Optional[str] = Field(default=None, description="别名,标记用,无实际意义")
         | 
| 31 127 | 
             
                base_url: str = Field(default="https://oapi.dingtalk.com/robot/send", description="Webhook的通用地址,无需更改")
         | 
| 32 128 |  | 
| 33 129 | 
             
                def webhook_url(self):
         | 
| @@ -37,7 +133,7 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 37 133 | 
             
                    钉钉API文档:
         | 
| 38 134 | 
             
                    https://open.dingtalk.com/document/robots/custom-robot-access
         | 
| 39 135 |  | 
| 40 | 
            -
                     | 
| 136 | 
            +
                    :return:
         | 
| 41 137 | 
             
                        str: Webhook URL
         | 
| 42 138 | 
             
                    """
         | 
| 43 139 | 
             
                    if self.secret is None:
         | 
| @@ -55,14 +151,28 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 55 151 | 
             
                    """
         | 
| 56 152 | 
             
                    从文本截取前12个字符作为标题,并清理其中的 Markdown 格式字符。
         | 
| 57 153 |  | 
| 58 | 
            -
                     | 
| 59 | 
            -
                        text: 原文
         | 
| 154 | 
            +
                    :param text: 原文
         | 
| 60 155 |  | 
| 61 | 
            -
                     | 
| 156 | 
            +
                    :return:
         | 
| 62 157 | 
             
                        str: 标题
         | 
| 63 158 | 
             
                    """
         | 
| 64 159 | 
             
                    return re.sub(r'[\n#>* ]', '', text[:12])
         | 
| 65 160 |  | 
| 161 | 
            +
                @staticmethod
         | 
| 162 | 
            +
                def __handle_response(response):
         | 
| 163 | 
            +
                    """
         | 
| 164 | 
            +
                    处理钉钉 Webhook API 响应,根据 errcode 抛出相应的异常
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                    :param response: 钉钉Webhook API响应的JSON数据
         | 
| 167 | 
            +
                    """
         | 
| 168 | 
            +
                    errcode = response.json().get("errcode")
         | 
| 169 | 
            +
                    errmsg = response.json().get("errmsg")
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    if errcode == "0":
         | 
| 172 | 
            +
                        return True
         | 
| 173 | 
            +
                    else:
         | 
| 174 | 
            +
                        raise DingTalkWebhookException(errcode, errmsg)
         | 
| 175 | 
            +
             | 
| 66 176 | 
             
                def send_text(self, text: str,
         | 
| 67 177 | 
             
                              at_mobiles:List[str]|None = None, at_user_ids:List[str]|None = None, at_all:bool = False):
         | 
| 68 178 | 
             
                    """
         | 
| @@ -71,18 +181,21 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 71 181 | 
             
                    钉钉API文档:
         | 
| 72 182 | 
             
                    https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type
         | 
| 73 183 |  | 
| 74 | 
            -
                     | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
             | 
| 78 | 
            -
             | 
| 184 | 
            +
                    :param str text: 待发送文本
         | 
| 185 | 
            +
                    :param List[str],optional at_mobiles: 需要 @ 的用户手机号。默认值为 None
         | 
| 186 | 
            +
                    :param List[str],optional at_user_ids: 需要 @ 的用户 UserID。默认值为 None
         | 
| 187 | 
            +
                    :param bool,optional at_all: 是否 @ 所有人。默认值为 False
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    :raise DingTalkWebhookException:
         | 
| 190 | 
            +
             | 
| 79 191 | 
             
                    """
         | 
| 80 192 | 
             
                    at = {"isAtAll": at_all}
         | 
| 81 193 | 
             
                    if at_user_ids:
         | 
| 82 194 | 
             
                        at["atUserIds"] = at_user_ids
         | 
| 83 195 | 
             
                    if at_mobiles:
         | 
| 84 196 | 
             
                        at["atMobiles"] = at_mobiles
         | 
| 85 | 
            -
                    httpx.post(url=self.webhook_url(), json={"msgtype": "text", "text": {"content": text}, "at": at})
         | 
| 197 | 
            +
                    response = httpx.post(url=self.webhook_url(), json={"msgtype": "text", "text": {"content": text}, "at": at})
         | 
| 198 | 
            +
                    self.__handle_response(response)
         | 
| 86 199 |  | 
| 87 200 | 
             
                def send_link(self, text: str, message_url:str, title:str|None = None, pic_url:str = ""):
         | 
| 88 201 | 
             
                    """
         | 
| @@ -91,31 +204,26 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 91 204 | 
             
                    钉钉API文档:
         | 
| 92 205 | 
             
                    https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type
         | 
| 93 206 |  | 
| 94 | 
            -
                     | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 207 | 
            +
                    :param str text: 待发送文本
         | 
| 208 | 
            +
                    :param str message_url: 链接的 Url
         | 
| 209 | 
            +
                    :param str,optional title: 标题,在通知和被引用时显示的简短信息。默认从文本中生成。
         | 
| 210 | 
            +
                    :param str,optional pic_url: 图片的 Url,默认为 None
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    :raise DingTalkWebhookException:
         | 
| 99 213 | 
             
                    """
         | 
| 100 214 | 
             
                    title = title or self._gen_title(text)
         | 
| 101 | 
            -
                    httpx.post(
         | 
| 215 | 
            +
                    response = httpx.post(
         | 
| 102 216 | 
             
                        url=self.webhook_url(),
         | 
| 103 217 | 
             
                        json={"msgtype": "link", "link": {"title": title, "text": text, "picUrl": pic_url, "messageUrl": message_url}}
         | 
| 104 218 | 
             
                    )
         | 
| 219 | 
            +
                    self.__handle_response(response)
         | 
| 105 220 |  | 
| 106 221 | 
             
                def send_markdown(self, text: List[str]|str, title:str|None = None,
         | 
| 107 222 | 
             
                                  at_mobiles:List[str]|None = None, at_user_ids:List[str]|None = None, at_all:bool = False):
         | 
| 108 223 | 
             
                    """
         | 
| 109 224 | 
             
                    发送 Markdown 消息。支持的语法有:
         | 
| 110 | 
            -
                        # 一级标题
         | 
| 111 | 
            -
                         | 
| 112 | 
            -
                        ### 三级标题
         | 
| 113 | 
            -
                        #### 四级标题
         | 
| 114 | 
            -
                        ##### 五级标题
         | 
| 115 | 
            -
                        ###### 六级标题
         | 
| 116 | 
            -
                        > 引用
         | 
| 117 | 
            -
                        **加粗**
         | 
| 118 | 
            -
                        *斜体*
         | 
| 225 | 
            +
                        # 一级标题  ## 二级标题  ### 三级标题  #### 四级标题  ##### 五级标题  ###### 六级标题
         | 
| 226 | 
            +
                        > 引用  **加粗**  *斜体*
         | 
| 119 227 | 
             
                        [链接跳转](https://example.com/doc.html)
         | 
| 120 228 | 
             
                        
         | 
| 121 229 | 
             
                        - 无序列表
         | 
| @@ -124,12 +232,13 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 124 232 | 
             
                    钉钉API文档:
         | 
| 125 233 | 
             
                    https://open.dingtalk.com/document/orgapp/custom-bot-send-message-type
         | 
| 126 234 |  | 
| 127 | 
            -
                     | 
| 128 | 
            -
             | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 235 | 
            +
                    :param str text: 待发送文本
         | 
| 236 | 
            +
                    :param str,optional title: 标题,在通知和被引用时显示的简短信息。默认从文本中生成。
         | 
| 237 | 
            +
                    :param List[str],optional at_mobiles: 需要 @ 的用户手机号。默认值为 None
         | 
| 238 | 
            +
                    :param List[str],optional at_user_ids: 需要 @ 的用户 UserID。默认值为 None
         | 
| 239 | 
            +
                    :param bool,optional at_all: 是否 @ 所有人。默认值为 False
         | 
| 240 | 
            +
             | 
| 241 | 
            +
                    :raise DingTalkWebhookException:
         | 
| 133 242 | 
             
                    """
         | 
| 134 243 | 
             
                    text = text if isinstance(text, str) else "\n\n".join(text)
         | 
| 135 244 | 
             
                    title = title or self._gen_title(text)
         | 
| @@ -138,43 +247,44 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 138 247 | 
             
                        at["atUserIds"] = at_user_ids
         | 
| 139 248 | 
             
                    if at_mobiles:
         | 
| 140 249 | 
             
                        at["atMobiles"] = at_mobiles
         | 
| 141 | 
            -
                    httpx.post(url=self.webhook_url(),json={"msgtype": "markdown", "markdown": {"title": title, "text": text}, "at": at})
         | 
| 250 | 
            +
                    response = httpx.post(url=self.webhook_url(),json={"msgtype": "markdown", "markdown": {"title": title, "text": text}, "at": at})
         | 
| 251 | 
            +
                    self.__handle_response(response)
         | 
| 142 252 |  | 
| 143 253 | 
             
                def send_action_card(self, text: List[str]|str, title:str|None = None, btn_orientation:Literal["0","1"] = "0",
         | 
| 144 254 | 
             
                                     single_jump:Tuple[str,str]|None = None, btns_jump:List[Tuple[str,str]]|None = None):
         | 
| 145 255 | 
             
                    """
         | 
| 146 256 | 
             
                    发送跳转 ActionCard 消息。
         | 
| 147 257 |  | 
| 148 | 
            -
                     | 
| 149 | 
            -
             | 
| 150 | 
            -
                        title (str, optional): 标题,在通知和被引用时显示的简短信息。默认从文本中生成。
         | 
| 151 | 
            -
                        btn_orientation (str, optional):  按钮排列方式,0-按钮竖直排列,1-按钮横向排列。默认值为 "0"
         | 
| 152 | 
            -
                        single_jump(Tuple[str,str], optional): 传此参数为单个按钮,元组内第一项为按钮的标题,第二项为按钮链接。
         | 
| 153 | 
            -
                        btns_jump(Tuple[str,str], optional): 传此参数为多个按钮,元组内第一项为按钮的标题,第二项为按钮链接。
         | 
| 258 | 
            +
                    **注意:**
         | 
| 259 | 
            +
                    同时传 `single_jump` 和 `btns_jump`,仅 `single_jump` 生效。
         | 
| 154 260 |  | 
| 155 | 
            -
                     | 
| 156 | 
            -
             | 
| 261 | 
            +
                    :param str text: 待发送文本,支持 Markdown 部分语法。
         | 
| 262 | 
            +
                    :param str,optional title: 标题,在通知和被引用时显示的简短信息。默认从文本中生成。
         | 
| 263 | 
            +
                    :param str,optional btn_orientation: 按钮排列方式,0-按钮竖直排列,1-按钮横向排列。默认值为 "0"
         | 
| 264 | 
            +
                    :param Tuple[str,str],optional single_jump: 传此参数为单个按钮,元组内第一项为按钮的标题,第二项为按钮链接。
         | 
| 265 | 
            +
                    :param Tuple[str,str],optional btns_jump: 传此参数为多个按钮,元组内第一项为按钮的标题,第二项为按钮链接。
         | 
| 157 266 |  | 
| 267 | 
            +
                    :raise DingTalkWebhookException:
         | 
| 158 268 | 
             
                    """
         | 
| 159 269 | 
             
                    text = text if isinstance(text, str) else "\n\n".join(text)
         | 
| 160 270 | 
             
                    title = title or self._gen_title(text)
         | 
| 161 271 | 
             
                    action_card = {"title": title, "text": text, "btnOrientation": str(btn_orientation)}
         | 
| 272 | 
            +
             | 
| 162 273 | 
             
                    if single_jump:
         | 
| 163 274 | 
             
                        action_card["singleTitle"], action_card["singleURL"] = single_jump
         | 
| 164 | 
            -
             | 
| 165 | 
            -
                    elif btns_jump:
         | 
| 275 | 
            +
                    if btns_jump:
         | 
| 166 276 | 
             
                        action_card["btns"] = [{"title": action_title, "actionURL": action_url} for action_title, action_url in btns_jump]
         | 
| 167 | 
            -
             | 
| 168 | 
            -
                     | 
| 169 | 
            -
             | 
| 277 | 
            +
             | 
| 278 | 
            +
                    response = httpx.post(url=self.webhook_url(), json={"msgtype": "actionCard", "actionCard": action_card})
         | 
| 279 | 
            +
                    self.__handle_response(response)
         | 
| 170 280 |  | 
| 171 281 | 
             
                def send_feed_card(self, *args:Tuple[str,str,str]):
         | 
| 172 282 | 
             
                    """
         | 
| 173 283 | 
             
                    发送 FeedCard 消息。
         | 
| 174 284 |  | 
| 175 | 
            -
                     | 
| 176 | 
            -
                        args (Tuple[str,str,str]): 可以传入任意个具有三个元素的元组,分别为 (标题, 跳转链接, 缩略图链接)
         | 
| 285 | 
            +
                    :param Tuple[str,str,str],optional args: 可以传入任意个具有三个元素的元组,分别为 `(标题, 跳转链接, 缩略图链接)`
         | 
| 177 286 |  | 
| 287 | 
            +
                    :raise DingTalkWebhookException:
         | 
| 178 288 | 
             
                    """
         | 
| 179 289 | 
             
                    for a in args:
         | 
| 180 290 | 
             
                        print(a)
         | 
| @@ -182,23 +292,100 @@ class DingTalkWebhookResource(ConfigurableResource): | |
| 182 292 | 
             
                        {"title": title, "messageURL": message_url, "picURL": pic_url}
         | 
| 183 293 | 
             
                        for title, message_url, pic_url in args
         | 
| 184 294 | 
             
                    ]
         | 
| 185 | 
            -
                    httpx.post(url=self.webhook_url(), json={"msgtype": "feedCard", "feedCard": {"links": links_data}})
         | 
| 295 | 
            +
                    response = httpx.post(url=self.webhook_url(), json={"msgtype": "feedCard", "feedCard": {"links": links_data}})
         | 
| 296 | 
            +
                    self.__handle_response(response)
         | 
| 186 297 |  | 
| 187 298 |  | 
| 188 299 | 
             
            class DingTalkAppResource(ConfigurableResource):
         | 
| 189 300 | 
             
                """
         | 
| 190 301 | 
             
                [钉钉服务端 API](https://open.dingtalk.com/document/orgapp/api-overview) 企业内部应用部分的第三方封装。
         | 
| 191 | 
            -
                通过此资源,可以调用部分钉钉服务端API。
         | 
| 192 302 |  | 
| 193 | 
            -
                 | 
| 194 | 
            -
             | 
| 303 | 
            +
                通过此资源,可以调用部分钉钉服务端 API。具体封装的 API 可以在 IDE 中通过引入 `DingTalkAppClient` 类来查看 IDE 提示:
         | 
| 304 | 
            +
             | 
| 305 | 
            +
                `from dagster_dingtalk import DingTalkAppClient`
         | 
| 306 | 
            +
             | 
| 307 | 
            +
                ### 配置项:
         | 
| 308 | 
            +
             | 
| 309 | 
            +
                - **AppID** (str):
         | 
| 310 | 
            +
                    应用应用唯一标识 AppID,作为缓存标识符使用。不传入则不缓存鉴权。
         | 
| 311 | 
            +
                - **AgentID** (int, optional):
         | 
| 312 | 
            +
                    原企业内部应用 AgentId ,部分 API 会使用到。默认值为 None
         | 
| 313 | 
            +
                - **AppName** (str, optional):
         | 
| 314 | 
            +
                    应用名。
         | 
| 315 | 
            +
                - **ClientId** (str):
         | 
| 316 | 
            +
                    应用的 Client ID ,原 AppKey 和 SuiteKey
         | 
| 317 | 
            +
                - **ClientSecret** (str):
         | 
| 318 | 
            +
                    应用的 Client Secret ,原 AppSecret 和 SuiteSecret
         | 
| 319 | 
            +
             | 
| 320 | 
            +
                ### 用例:
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                1. 使用单一的企业内部应用资源。
         | 
| 323 | 
            +
             | 
| 324 | 
            +
                ```python
         | 
| 325 | 
            +
                from dagster_dingtalk import DingTalkAppResource
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                @op(required_resource_keys={"dingtalk"}, ins={"user_id": In(str)})
         | 
| 328 | 
            +
                def op_user_info(context:OpExecutionContext, user_id:str):
         | 
| 329 | 
            +
                    dingtalk:DingTalkAppClient = context.resources.dingtalk
         | 
| 330 | 
            +
                    result = dingtalk.通讯录管理.用户管理.查询用户详情(user_id).get('result')
         | 
| 331 | 
            +
                    context.log.info(result)
         | 
| 332 | 
            +
             | 
| 333 | 
            +
                @job
         | 
| 334 | 
            +
                def job_user_info():
         | 
| 335 | 
            +
                    op_user_info
         | 
| 336 | 
            +
             | 
| 337 | 
            +
                defs = Definitions(jobs=job_user_info, resources={
         | 
| 338 | 
            +
                    "dingtalk": DingTalkAppResource(
         | 
| 339 | 
            +
                        AppID = "<the-app-id>",
         | 
| 340 | 
            +
                        ClientId = "<the-client-id>",
         | 
| 341 | 
            +
                        ClientSecret = EnvVar("<the-client-secret-env-name>"),
         | 
| 342 | 
            +
                    )
         | 
| 343 | 
            +
                })
         | 
| 344 | 
            +
                ```
         | 
| 345 | 
            +
             | 
| 346 | 
            +
                2. 启动时动态构建企业内部应用资源, 可参考 [Dagster文档 | 在启动时配置资源](https://docs.dagster.io/concepts/resources#configuring-resources-at-launch-time)
         | 
| 347 | 
            +
             | 
| 348 | 
            +
                ```python
         | 
| 349 | 
            +
                from dagster_dingtalk import DingTalkAppResource
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                @op(required_resource_keys={"dingtalk"}, ins={"user_id": In(str)})
         | 
| 352 | 
            +
                def op_user_info(context:OpExecutionContext, user_id:str):
         | 
| 353 | 
            +
                    dingtalk:DingTalkAppClient = context.resources.dingtalk
         | 
| 354 | 
            +
                    result = dingtalk.通讯录管理.用户管理.查询用户详情(user_id).get('result')
         | 
| 355 | 
            +
                    context.log.info(result)
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                @job
         | 
| 358 | 
            +
                def job_user_info():
         | 
| 359 | 
            +
                    op_user_info()
         | 
| 360 | 
            +
             | 
| 361 | 
            +
                dingtalk_apps = {
         | 
| 362 | 
            +
                    "App1" : DingTalkAppResource(
         | 
| 363 | 
            +
                        AppID = "<app-1-app-id>",
         | 
| 364 | 
            +
                        ClientId = "<app-1-client-id>",
         | 
| 365 | 
            +
                        ClientSecret = EnvVar("<app-1-client-secret-env-name>"),
         | 
| 366 | 
            +
                    ),
         | 
| 367 | 
            +
                    "App2" : DingTalkAppResource(
         | 
| 368 | 
            +
                        AppID = "<app-2-app-id>",
         | 
| 369 | 
            +
                        ClientId = "<app-2-client-id>",
         | 
| 370 | 
            +
                        ClientSecret = EnvVar("<app-2-client-secret-env-name>"),
         | 
| 371 | 
            +
                    )
         | 
| 372 | 
            +
                }
         | 
| 373 | 
            +
             | 
| 374 | 
            +
                defs = Definitions(jobs=job_user_info, resources={"dingtalk": DingTalkAppResource.configure_at_launch()})
         | 
| 375 | 
            +
             | 
| 376 | 
            +
                @schedule(cron_schedule="20 9 * * *", job=job_user_info)
         | 
| 377 | 
            +
                def schedule_user_info():
         | 
| 378 | 
            +
                    return RunRequest(run_config=RunConfig(
         | 
| 379 | 
            +
                        ops={"op_user_info": {"inputs": {"user_id": "<the-user-id>"}}},
         | 
| 380 | 
            +
                        resources={"dingtalk": dingtalk_apps["App1"]},
         | 
| 381 | 
            +
                    ))
         | 
| 382 | 
            +
                ```
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                ### 注意:
         | 
| 385 | 
            +
             | 
| 386 | 
            +
                应该永远避免直接将密钥字符串直接配置给资源,这会导致在 dagster 前端用户界面暴露密钥。
         | 
| 387 | 
            +
                应当从环境变量中读取密钥。你可以在代码中注册临时的环境变量,或从系统中引入环境变量。
         | 
| 195 388 |  | 
| 196 | 
            -
                Args:
         | 
| 197 | 
            -
                    AppID (str): 应用应用唯一标识 AppID,作为缓存标识符使用。不传入则不缓存鉴权。
         | 
| 198 | 
            -
                    AgentID (int, optional): 原企业内部应用 AgentId ,部分 API 会使用到。默认值为 None
         | 
| 199 | 
            -
                    AppName (str, optional): 应用名。
         | 
| 200 | 
            -
                    ClientId (str): 应用的 Client ID ,原 AppKey 和 SuiteKey
         | 
| 201 | 
            -
                    ClientSecret (str): 应用的 Client Secret ,原 AppSecret 和 SuiteSecret
         | 
| 202 389 | 
             
                """
         | 
| 203 390 |  | 
| 204 391 | 
             
                AppID: str = Field(description="应用应用唯一标识 AppID,作为缓存标识符使用。不传入则不缓存鉴权。")
         | 
| @@ -212,6 +399,11 @@ class DingTalkAppResource(ConfigurableResource): | |
| 212 399 | 
             
                    return False
         | 
| 213 400 |  | 
| 214 401 | 
             
                def create_resource(self, context: InitResourceContext) -> DingTalkClient:
         | 
| 402 | 
            +
                    """
         | 
| 403 | 
            +
                    返回一个 `DingTalkClient` 实例。
         | 
| 404 | 
            +
                    :param context:
         | 
| 405 | 
            +
                    :return:
         | 
| 406 | 
            +
                    """
         | 
| 215 407 | 
             
                    return DingTalkClient(
         | 
| 216 408 | 
             
                        app_id=self.AppID,
         | 
| 217 409 | 
             
                        agent_id=self.AgentID,
         | 
    
        dagster_dingtalk/version.py
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            __version__ = "0.1. | 
| 1 | 
            +
            __version__ = "0.1.11"
         | 
| @@ -0,0 +1,8 @@ | |
| 1 | 
            +
            dagster_dingtalk/__init__.py,sha256=X7r8JoydXOsT9Sis4rBpVSKQeKJnnZ_t_qFae-ASF7E,466
         | 
| 2 | 
            +
            dagster_dingtalk/app_client.py,sha256=xZ9NcVZuWvz7S2thm4B1h4aFiBXmUnkp5nK5N_k7HwE,14011
         | 
| 3 | 
            +
            dagster_dingtalk/operations.py,sha256=3cCZCxh-dAdzzb75WCTKVdzeMV8yu_JJpIeULS7XaNg,761
         | 
| 4 | 
            +
            dagster_dingtalk/resources.py,sha256=XXGRub47EZY2MfAsGhyEZ_Joh_D-BMFNh_jXCVPx6tU,17255
         | 
| 5 | 
            +
            dagster_dingtalk/version.py,sha256=nllDrH0jyChMuuYrK0CC55iTBKUNTUjejtcwxyUF2EQ,23
         | 
| 6 | 
            +
            dagster_dingtalk-0.1.11.dist-info/METADATA,sha256=AqQfS-lsnBW-PBoad9xIsZei9b995Yf4SJhaHq1YQ7c,1221
         | 
| 7 | 
            +
            dagster_dingtalk-0.1.11.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
         | 
| 8 | 
            +
            dagster_dingtalk-0.1.11.dist-info/RECORD,,
         | 
| @@ -1,8 +0,0 @@ | |
| 1 | 
            -
            dagster_dingtalk/__init__.py,sha256=TUfdRP1n-tifjBuC9ugDxzeNu-BtjmX2fEFDgTitHeo,390
         | 
| 2 | 
            -
            dagster_dingtalk/app_client.py,sha256=nti06ZM_LKvdDkHryt1-beIKZrYcOkUKDznXXnqn8qg,6747
         | 
| 3 | 
            -
            dagster_dingtalk/operations.py,sha256=3cCZCxh-dAdzzb75WCTKVdzeMV8yu_JJpIeULS7XaNg,761
         | 
| 4 | 
            -
            dagster_dingtalk/resources.py,sha256=SuQiNvq50eqdGT3bT0eiQ7whv6CGp8rZsqfHL6gBtf4,9780
         | 
| 5 | 
            -
            dagster_dingtalk/version.py,sha256=Nw9m62L0Jey5qchmcQFQeIRAFUlcjZewmyhYlfs0pG0,25
         | 
| 6 | 
            -
            dagster_dingtalk-0.1.10b1.dist-info/METADATA,sha256=m6GrwJ60HSHFP4UfVSO6y5ybcIwFs_8TXNowkjjKRnw,1223
         | 
| 7 | 
            -
            dagster_dingtalk-0.1.10b1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
         | 
| 8 | 
            -
            dagster_dingtalk-0.1.10b1.dist-info/RECORD,,
         | 
| 
            File without changes
         |