Coverage for mindsdb / integrations / handlers / ms_teams_handler / ms_graph_api_teams_client.py: 86%

139 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-21 00:36 +0000

1from abc import ABC, abstractmethod 

2from typing import Text, List, Dict 

3 

4from requests.exceptions import RequestException 

5 

6from mindsdb.integrations.utilities.handlers.api_utilities.microsoft.ms_graph_api_utilities import MSGraphAPIBaseClient 

7from mindsdb.utilities import log 

8 

9 

10logger = log.getLogger(__name__) 

11 

12 

13class MSGraphAPITeamsClient(MSGraphAPIBaseClient, ABC): 

14 """ 

15 The base class for the Microsoft Graph API client for the Microsoft Teams handler. 

16 """ 

17 

18 def check_connection(self) -> bool: 

19 """ 

20 Check if the connection to Microsoft Teams is established. 

21 

22 Returns: 

23 bool: True if the connection is established, False otherwise. 

24 """ 

25 try: 

26 self._get_all_groups() 

27 return True 

28 except RequestException as request_error: 

29 logger.error(f"Failed to check connection to Microsoft Teams: {request_error}") 

30 return False 

31 

32 def get_teams(self) -> List[Dict]: 

33 """ 

34 Get teams from Microsoft Teams. 

35 

36 Returns: 

37 List[Dict]: The teams data. 

38 """ 

39 return self._get_all_groups() 

40 

41 def get_channels(self, group_id: Text = None, channel_ids: List[Text] = None) -> List[Dict]: 

42 """ 

43 Get channels from Microsoft Teams. 

44 

45 Args: 

46 group_id (Text): The ID of the group that the channels belong to. 

47 channel_ids (List[Text]): The IDs of the channels. 

48 

49 Returns: 

50 List[Dict]: The channels data. 

51 """ 

52 if group_id and channel_ids: 

53 return self._get_channels_in_group_by_ids(group_id, channel_ids) 

54 elif group_id: 

55 return self._get_all_channels_in_group(group_id) 

56 elif channel_ids: 

57 return self._get_channels_across_all_groups_by_ids(channel_ids) 

58 else: 

59 return self._get_all_channels_across_all_groups() 

60 

61 def get_channel_messages(self, group_id: Text, channel_id: Text, message_ids: List[Text] = None) -> List[Dict]: 

62 """ 

63 Get channel messages from Microsoft Teams. 

64 

65 Args: 

66 group_id (Text): The ID of the group that the channel belongs to. 

67 channel_id (Text): The ID of the channel that the messages belong to. 

68 message_ids (List[Text]): The IDs of the messages. 

69 

70 Returns: 

71 List[Dict]: The messages data. 

72 """ 

73 if message_ids: 

74 return self._get_messages_in_channel_by_ids(group_id, channel_id, message_ids) 

75 else: 

76 return self._get_all_messages_in_channel(group_id, channel_id) 

77 

78 def get_chats(self, chat_ids: List[Text] = None) -> List[Dict]: 

79 """ 

80 Get chats from Microsoft Teams. 

81 

82 Args: 

83 chat_ids (List[Text]): The IDs of the chats. 

84 

85 Returns: 

86 List[Dict]: The chats data. 

87 """ 

88 if chat_ids: 

89 return self._get_chats_by_ids(chat_ids) 

90 else: 

91 return self._get_all_chats() 

92 

93 def get_chat_messages(self, chat_id: Text, message_ids: List[Text] = None) -> List[Dict]: 

94 """ 

95 Get chat messages from Microsoft Teams. 

96 

97 Args: 

98 chat_id (Text): The ID of the chat that the messages belong to. 

99 message_ids (List[Text]): The IDs of the messages. 

100 

101 Returns: 

102 List[Dict]: The messages data. 

103 """ 

104 if message_ids: 

105 return self._get_messages_in_chat_by_ids(chat_id, message_ids) 

106 else: 

107 return self._get_all_messages_in_chat(chat_id) 

108 

109 def _get_all_group_ids(self) -> List[Text]: 

110 """ 

111 Get all group IDs related to Microsoft Teams. 

112 

113 Returns: 

114 List[Text]: The group IDs. 

115 """ 

116 if not self._group_ids: 116 ↛ 120line 116 didn't jump to line 120 because the condition on line 116 was always true

117 groups = self._get_all_groups() 

118 self._group_ids = [group["id"] for group in groups] 

119 

120 return self._group_ids 

121 

122 @abstractmethod 

123 def _get_all_groups(self) -> List[Dict]: 

124 """ 

125 Get all groups related to Microsoft Teams. 

126 

127 Returns: 

128 List[Dict]: The groups data. 

129 """ 

130 pass 

131 

132 @abstractmethod 

133 def _get_chat_by_id(self, chat_id: Text) -> Dict: 

134 """ 

135 Get a chat by its ID. 

136 

137 Args: 

138 chat_id (Text): The ID of the chat. 

139 

140 Returns: 

141 Dict: The chat data. 

142 """ 

143 pass 

144 

145 @abstractmethod 

146 def _get_all_chats(self, limit: int = None) -> List[Dict]: 

147 """ 

148 Get all chats related to Microsoft Teams. 

149 

150 Args: 

151 limit (int): The maximum number of chats to return. 

152 

153 Returns: 

154 List[Dict]: The chats data. 

155 """ 

156 pass 

157 

158 @abstractmethod 

159 def _get_message_in_chat_by_id(self, chat_id: Text, message_id: Text) -> Dict: 

160 """ 

161 Get a message by its ID and the ID of the chat that it belongs to. 

162 

163 Args: 

164 chat_id (Text): The ID of the chat that the message belongs to. 

165 message_id (Text): The ID of the message. 

166 

167 Returns: 

168 Dict: The message data. 

169 """ 

170 pass 

171 

172 @abstractmethod 

173 def _get_all_messages_in_chat(self, chat_id: Text, limit: int = None) -> List[Dict]: 

174 """ 

175 Get messages of a chat by its ID. 

176 

177 Args: 

178 chat_id (Text): The ID of the chat. 

179 

180 Returns: 

181 List[Dict]: The messages data. 

182 """ 

183 pass 

184 

185 def _get_channel_in_group_by_id(self, group_id: Text, channel_id: Text) -> Dict: 

186 """ 

187 Get a channel by its ID and the ID of the group that it belongs to. 

188 

189 Args: 

190 group_id (Text): The ID of the group that the channel belongs to. 

191 channel_id (Text): The ID of the channel. 

192 

193 Returns: 

194 Dict: The channel data. 

195 """ 

196 channel = self.fetch_data_json(f"teams/{group_id}/channels/{channel_id}") 

197 # Add the group ID to the channel data. 

198 channel.update({"teamId": group_id}) 

199 

200 return channel 

201 

202 def _get_channels_in_group_by_ids(self, group_id: Text, channel_ids: List[Text]) -> List[Dict]: 

203 """ 

204 Get channels by their IDs and the ID of the group that they belong to. 

205 

206 Args: 

207 group_id (Text): The ID of the group that the channels belong to. 

208 channel_ids (List[Text]): The IDs of the channels. 

209 

210 Returns: 

211 List[Dict]: The channels data. 

212 """ 

213 channels = [] 

214 for channel_id in channel_ids: 

215 channels.append(self._get_channel_in_group_by_id(group_id, channel_id)) 

216 

217 return channels 

218 

219 def _get_all_channels_in_group(self, group_id: Text) -> List[Dict]: 

220 """ 

221 Get all channels of a group by its ID. 

222 

223 Args: 

224 group_id (Text): The ID of the group. 

225 

226 Returns: 

227 List[Dict]: The channels data. 

228 """ 

229 channels = self.fetch_data_json(f"teams/{group_id}/channels") 

230 for channel in channels: 

231 channel["teamId"] = group_id 

232 

233 return channels 

234 

235 def _get_all_channels_across_all_groups(self) -> List[Dict]: 

236 """ 

237 Get all channels across all groups related to Microsoft Teams. 

238 

239 Returns: 

240 List[Dict]: The channels data. 

241 """ 

242 channels = [] 

243 for group_id in self._get_all_group_ids(): 

244 channels += self._get_all_channels_in_group(group_id) 

245 

246 return channels 

247 

248 def _get_channels_across_all_groups_by_ids(self, channel_ids: List[Text]) -> List[Dict]: 

249 """ 

250 Get channels by their IDs. 

251 

252 Args: 

253 channel_ids (List[Text]): The IDs of the channels. 

254 

255 Returns: 

256 List[Dict]: The channels data. 

257 """ 

258 channels = self._get_all_channels_across_all_groups() 

259 

260 return [channel for channel in channels if channel["id"] in channel_ids] 

261 

262 def _get_message_in_channel_by_id(self, group_id: Text, channel_id: Text, message_id: Text) -> Dict: 

263 """ 

264 Get a message by its ID, the ID of the group that it belongs to, and the ID of the channel that it belongs to. 

265 

266 Args: 

267 group_id (Text): The ID of the group that the channel belongs to. 

268 channel_id (Text): The ID of the channel that the message belongs to. 

269 message_id (Text): The ID of the message. 

270 

271 Returns: 

272 Dict: The message data. 

273 """ 

274 return self.fetch_data_json(f"teams/{group_id}/channels/{channel_id}/messages/{message_id}") 

275 

276 def _get_messages_in_channel_by_ids(self, group_id: Text, channel_id: Text, message_ids: List[Text]) -> List[Dict]: 

277 """ 

278 Get messages by their IDs, the ID of the group that they belong to, and the ID of the channel that they belong to. 

279 

280 Args: 

281 group_id (Text): The ID of the group that the channel belongs to. 

282 channel_id (Text): The ID of the channel that the messages belong to. 

283 message_ids (List[Text]): The IDs of the messages. 

284 

285 Returns: 

286 List[Dict]: The messages data. 

287 """ 

288 messages = [] 

289 for message_id in message_ids: 

290 messages.append(self._get_message_in_channel_by_id(group_id, channel_id, message_id)) 

291 

292 return messages 

293 

294 def _get_all_messages_in_channel(self, group_id: Text, channel_id: Text, limit: int = None) -> List[Dict]: 

295 """ 

296 Get messages of a channel by its ID and the ID of the group that it belongs to. 

297 

298 Args: 

299 group_id (Text): The ID of the group that the channel belongs to. 

300 channel_id (Text): The ID of the channel. 

301 

302 Returns: 

303 List[Dict]: The messages data. 

304 """ 

305 messages = [] 

306 for messages_batch in self.fetch_paginated_data(f"teams/{group_id}/channels/{channel_id}/messages"): 

307 messages += messages_batch 

308 

309 if limit and len(messages) >= limit: 309 ↛ 310line 309 didn't jump to line 310 because the condition on line 309 was never true

310 break 

311 

312 return messages[:limit] 

313 

314 def _get_chats_by_ids(self, chat_ids: List[Text]) -> List[Dict]: 

315 """ 

316 Get chats by their IDs. 

317 

318 Args: 

319 chat_ids (List[Text]): The IDs of the chats. 

320 

321 Returns: 

322 List[Dict]: The chats data. 

323 """ 

324 chats = [] 

325 for chat_id in chat_ids: 

326 chats.append(self._get_chat_by_id(chat_id)) 

327 

328 return chats 

329 

330 def _get_messages_in_chat_by_ids(self, chat_id: Text, message_ids: List[Text]) -> List[Dict]: 

331 """ 

332 Get messages by their IDs and the ID of the chat that they belong to. 

333 

334 Args: 

335 chat_id (Text): The ID of the chat that the messages belong to. 

336 message_ids (List[Text]): The IDs of the messages. 

337 

338 Returns: 

339 List[Dict]: The messages data. 

340 """ 

341 messages = [] 

342 for message_id in message_ids: 

343 messages.append(self._get_message_in_chat_by_id(chat_id, message_id)) 

344 

345 return messages 

346 

347 

348class MSGraphAPITeamsApplicationPermissionsClient(MSGraphAPITeamsClient): 

349 """ 

350 The Microsoft Graph API client for the Microsoft Teams handler with application permissions. 

351 This client is used for accessing the Microsoft Teams specific endpoints of the Microsoft Graph API. 

352 Several common methods for submitting requests, fetching data, etc. are inherited from the base class. 

353 """ 

354 

355 def _get_all_groups(self) -> List[Dict]: 

356 """ 

357 Get all groups related to Microsoft Teams. 

358 

359 Returns: 

360 List[Dict]: The groups data. 

361 """ 

362 return self.fetch_data_json( 

363 "/groups", 

364 params={"$filter": "resourceProvisioningOptions/Any(x:x eq 'Team')"} 

365 ) 

366 

367 def _get_chat_by_id(self, chat_id: Text) -> Dict: 

368 """ 

369 Get a chat by its ID. 

370 

371 Args: 

372 chat_id (Text): The ID of the chat. 

373 

374 Returns: 

375 Dict: The chat data. 

376 """ 

377 return self.fetch_data_json(f"chats/{chat_id}") 

378 

379 def _get_all_chats(self, limit: int = None) -> List[Dict]: 

380 """ 

381 Get all chats related to Microsoft Teams. 

382 

383 Args: 

384 limit (int): The maximum number of chats to return. 

385 

386 Returns: 

387 List[Dict]: The chats data. 

388 """ 

389 raise RuntimeError("Retrieving all chats is not supported with application permissions. Either use delegated permissions or provide a chat ID.") 

390 

391 def _get_message_in_chat_by_id(self, chat_id: Text, message_id: Text) -> Dict: 

392 """ 

393 Get a message by its ID and the ID of the chat that it belongs to. 

394 

395 Args: 

396 chat_id (Text): The ID of the chat that the message belongs to. 

397 message_id (Text): The ID of the message. 

398 

399 Returns: 

400 Dict: The message data. 

401 """ 

402 return self.fetch_data_json(f"chats/{chat_id}/messages/{message_id}") 

403 

404 def _get_all_messages_in_chat(self, chat_id: Text, limit: int = None) -> List[Dict]: 

405 """ 

406 Get messages of a chat by its ID. 

407 

408 Args: 

409 chat_id (Text): The ID of the chat. 

410 

411 Returns: 

412 List[Dict]: The messages data. 

413 """ 

414 messages = [] 

415 for messages_batch in self.fetch_paginated_data(f"chats/{chat_id}/messages"): 

416 messages += messages_batch 

417 

418 if limit and len(messages) >= limit: 

419 break 

420 

421 return messages[:limit] 

422 

423 

424class MSGraphAPITeamsDelegatedPermissionsClient(MSGraphAPITeamsClient): 

425 """ 

426 The Microsoft Graph API client for the Microsoft Teams handler with delegated permissions. 

427 This client is used for accessing the Microsoft Teams specific endpoints of the Microsoft Graph API. 

428 Several common methods for submitting requests, fetching data, etc. are inherited from the base class. 

429 """ 

430 

431 def _get_all_groups(self) -> List[Dict]: 

432 """ 

433 Get all groups that the signed in user is a member of. 

434 

435 Returns: 

436 List[Dict]: The groups data. 

437 """ 

438 return self.fetch_data_json("me/joinedTeams") 

439 

440 def _get_chat_by_id(self, chat_id: Text) -> Dict: 

441 """ 

442 Get a chat by its ID. 

443 

444 Args: 

445 chat_id (Text): The ID of the chat. 

446 

447 Returns: 

448 Dict: The chat data. 

449 """ 

450 return self.fetch_data_json(f"/me/chats/{chat_id}") 

451 

452 def _get_all_chats(self, limit: int = None) -> List[Dict]: 

453 """ 

454 Get all chats of the signed in user. 

455 

456 Args: 

457 limit (int): The maximum number of chats to return. 

458 

459 Returns: 

460 List[Dict]: The chats data. 

461 """ 

462 chats = [] 

463 for chat_batch in self.fetch_paginated_data("me/chats"): 

464 chats += chat_batch 

465 

466 if limit and len(chats) >= limit: 466 ↛ 467line 466 didn't jump to line 467 because the condition on line 466 was never true

467 break 

468 

469 return chats[:limit] 

470 

471 def _get_message_in_chat_by_id(self, chat_id: Text, message_id: Text) -> Dict: 

472 """ 

473 Get a message by its ID and the ID of the chat that it belongs to. 

474 

475 Args: 

476 chat_id (Text): The ID of the chat that the message belongs to. 

477 message_id (Text): The ID of the message. 

478 

479 Returns: 

480 Dict: The message data. 

481 """ 

482 return self.fetch_data_json(f"me/chats/{chat_id}/messages/{message_id}") 

483 

484 def _get_all_messages_in_chat(self, chat_id: Text, limit: int = None) -> List[Dict]: 

485 """ 

486 Get messages of a chat by its ID. 

487 

488 Args: 

489 chat_id (Text): The ID of the chat. 

490 

491 Returns: 

492 List[Dict]: The messages data. 

493 """ 

494 messages = [] 

495 for messages_batch in self.fetch_paginated_data(f"me/chats/{chat_id}/messages"): 

496 messages += messages_batch 

497 

498 if limit and len(messages) >= limit: 498 ↛ 499line 498 didn't jump to line 499 because the condition on line 498 was never true

499 break 

500 

501 return messages[:limit]