Coverage for mindsdb / integrations / handlers / google_calendar_handler / google_calendar_handler.py: 0%

137 statements  

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

1import pandas as pd 

2from googleapiclient.discovery import build 

3 

4from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE 

5from mindsdb.integrations.libs.api_handler import APIHandler, FuncParser 

6from mindsdb.integrations.libs.response import ( 

7 HandlerStatusResponse as StatusResponse, 

8 HandlerResponse as Response, 

9) 

10from mindsdb.utilities.config import Config 

11from mindsdb.utilities import log 

12from mindsdb.integrations.utilities.handlers.auth_utilities.google import GoogleUserOAuth2Manager 

13from mindsdb.integrations.utilities.handlers.auth_utilities.exceptions import AuthException 

14 

15from .google_calendar_tables import GoogleCalendarEventsTable 

16 

17DEFAULT_SCOPES = [ 

18 "https://www.googleapis.com/auth/calendar", 

19 "https://www.googleapis.com/auth/calendar.events", 

20 "https://www.googleapis.com/auth/calendar.readonly", 

21] 

22 

23logger = log.getLogger(__name__) 

24 

25 

26class GoogleCalendarHandler(APIHandler): 

27 """ 

28 A class for handling connections and interactions with the Google Calendar API. 

29 """ 

30 

31 name = "google_calendar" 

32 

33 def __init__(self, name: str, **kwargs): 

34 """constructor 

35 Args: 

36 name (str): the handler name 

37 credentials_file (str): The path to the credentials file. 

38 scopes (list): The list of scopes to use for authentication. 

39 is_connected (bool): Whether the API client is connected to Google Calendar. 

40 events (GoogleCalendarEventsTable): The `GoogleCalendarEventsTable` object for interacting with the events table. 

41 """ 

42 super().__init__(name) 

43 self.connection_data = kwargs.get("connection_data", {}) 

44 

45 self.service = None 

46 self.is_connected = False 

47 

48 self.handler_storage = kwargs["handler_storage"] 

49 

50 self.credentials_url = self.connection_data.get("credentials_url", None) 

51 self.credentials_file = self.connection_data.get("credentials_file", None) 

52 if self.connection_data.get("credentials"): 

53 self.credentials_file = self.connection_data.pop("credentials") 

54 if not self.credentials_file and not self.credentials_url: 

55 # try to get from config 

56 gcalendar_config = Config().get("handlers", {}).get("youtube", {}) 

57 secret_file = gcalendar_config.get("credentials_file") 

58 secret_url = gcalendar_config.get("credentials_url") 

59 if secret_file: 

60 self.credentials_file = secret_file 

61 elif secret_url: 

62 self.credentials_url = secret_url 

63 

64 self.scopes = self.connection_data.get("scopes", DEFAULT_SCOPES) 

65 

66 events = GoogleCalendarEventsTable(self) 

67 self.events = events 

68 self._register_table("events", events) 

69 

70 def connect(self): 

71 """ 

72 Set up any connections required by the handler 

73 Should return output of check_connection() method after attempting 

74 connection. Should switch self.is_connected. 

75 Returns: 

76 HandlerStatusResponse 

77 """ 

78 if self.is_connected is True: 

79 return self.service 

80 

81 google_oauth2_manager = GoogleUserOAuth2Manager( 

82 self.handler_storage, 

83 self.scopes, 

84 self.credentials_file, 

85 self.credentials_url, 

86 self.connection_data.get("code"), 

87 ) 

88 creds = google_oauth2_manager.get_oauth2_credentials() 

89 

90 self.service = build("calendar", "v3", credentials=creds) 

91 return self.service 

92 

93 def check_connection(self) -> StatusResponse: 

94 """ 

95 Check connection to the handler 

96 Returns: 

97 HandlerStatusResponse 

98 """ 

99 

100 response = StatusResponse(False) 

101 

102 try: 

103 self.connect() 

104 response.success = True 

105 response.copy_storage = True 

106 

107 except AuthException as error: 

108 response.error_message = str(error) 

109 response.redirect_url = error.auth_url 

110 return response 

111 

112 except Exception as e: 

113 logger.error(f"Error connecting to Google Calendar API: {e}!") 

114 response.error_message = e 

115 

116 self.is_connected = response.success 

117 return response 

118 

119 def native_query(self, query: str = None) -> Response: 

120 """ 

121 Receive raw query and act upon it somehow. 

122 Args: 

123 query (Any): query in native format (str for sql databases, 

124 api's json etc) 

125 Returns: 

126 HandlerResponse 

127 """ 

128 method_name, params = FuncParser().from_string(query) 

129 

130 df = self.call_application_api(method_name, params) 

131 

132 return Response(RESPONSE_TYPE.TABLE, data_frame=df) 

133 

134 def get_events(self, params: dict = None) -> pd.DataFrame: 

135 """ 

136 Get events from Google Calendar API 

137 Args: 

138 params (dict): query parameters 

139 Returns: 

140 DataFrame 

141 """ 

142 service = self.connect() 

143 page_token = None 

144 events = pd.DataFrame(columns=self.events.get_columns()) 

145 while True: 

146 events_result = service.events().list(calendarId="primary", pageToken=page_token, **params).execute() 

147 events = pd.concat( 

148 [events, pd.DataFrame(events_result.get("items", []), columns=self.events.get_columns())], 

149 ignore_index=True, 

150 ) 

151 page_token = events_result.get("nextPageToken") 

152 if not page_token: 

153 break 

154 return events 

155 

156 def create_event(self, params: dict = None) -> pd.DataFrame: 

157 """ 

158 Create an event in the calendar. 

159 Args: 

160 params (dict): query parameters 

161 Returns: 

162 DataFrame 

163 """ 

164 service = self.connect() 

165 # Check if 'attendees' is a string and split it into a list 

166 if isinstance(params["attendees"], str): 

167 params["attendees"] = params["attendees"].split(",") 

168 

169 event = { 

170 "summary": params["summary"], 

171 "location": params["location"], 

172 "description": params["description"], 

173 "start": { 

174 "dateTime": params["start"]["dateTime"], 

175 "timeZone": params["start"]["timeZone"], 

176 }, 

177 "end": { 

178 "dateTime": params["end"]["dateTime"], 

179 "timeZone": params["end"]["timeZone"], 

180 }, 

181 "recurrence": ["RRULE:FREQ=DAILY;COUNT=1"], 

182 "attendees": [ 

183 {"email": attendee["email"]} 

184 for attendee in ( 

185 params["attendees"] if isinstance(params["attendees"], list) else [params["attendees"]] 

186 ) 

187 ], 

188 "reminders": { 

189 "useDefault": False, 

190 "overrides": [ 

191 {"method": "email", "minutes": 24 * 60}, 

192 {"method": "popup", "minutes": 10}, 

193 ], 

194 }, 

195 } 

196 

197 event = service.events().insert(calendarId="primary", body=event).execute() 

198 return pd.DataFrame([event], columns=self.events.get_columns()) 

199 

200 def update_event(self, params: dict = None) -> pd.DataFrame: 

201 """ 

202 Update event or events in the calendar. 

203 Args: 

204 params (dict): query parameters 

205 Returns: 

206 DataFrame 

207 """ 

208 service = self.connect() 

209 df = pd.DataFrame(columns=["eventId", "status"]) 

210 if params["event_id"]: 

211 start_id = int(params["event_id"]) 

212 end_id = start_id + 1 

213 elif not params["start_id"]: 

214 start_id = int(params["end_id"]) - 10 

215 elif not params["end_id"]: 

216 end_id = int(params["start_id"]) + 10 

217 else: 

218 start_id = int(params["start_id"]) 

219 end_id = int(params["end_id"]) 

220 

221 for i in range(start_id, end_id): 

222 event = service.events().get(calendarId="primary", eventId=i).execute() 

223 if params["summary"]: 

224 event["summary"] = params["summary"] 

225 if params["location"]: 

226 event["location"] = params["location"] 

227 if params["description"]: 

228 event["description"] = params["description"] 

229 if params["start"]: 

230 event["start"]["dateTime"] = params["start"]["dateTime"] 

231 event["start"]["timeZone"] = params["start"]["timeZone"] 

232 if params["end"]: 

233 event["end"]["dateTime"] = params["end"]["dateTime"] 

234 event["end"]["timeZone"] = params["end"]["timeZone"] 

235 if params["attendees"]: 

236 event["attendees"] = [{"email": attendee} for attendee in params["attendees"].split(",")] 

237 updated_event = service.events().update(calendarId="primary", eventId=event["id"], body=event).execute() 

238 df = pd.concat( 

239 [df, pd.DataFrame([{"eventId": updated_event["id"], "status": "updated"}])], ignore_index=True 

240 ) 

241 

242 return df 

243 

244 def delete_event(self, params): 

245 """ 

246 Delete event or events in the calendar. 

247 Args: 

248 params (dict): query parameters 

249 Returns: 

250 DataFrame 

251 """ 

252 service = self.connect() 

253 if params["event_id"]: 

254 service.events().delete(calendarId="primary", eventId=params["event_id"]).execute() 

255 return pd.DataFrame([{"eventId": params["event_id"], "status": "deleted"}]) 

256 else: 

257 df = pd.DataFrame(columns=["eventId", "status"]) 

258 if not params["start_id"]: 

259 start_id = int(params["end_id"]) - 10 

260 elif not params["end_id"]: 

261 end_id = int(params["start_id"]) + 10 

262 else: 

263 start_id = int(params["start_id"]) 

264 end_id = int(params["end_id"]) 

265 for i in range(start_id, end_id): 

266 service.events().delete(calendarId="primary", eventId=str(i)).execute() 

267 df = pd.concat([df, pd.DataFrame([{"eventId": str(i), "status": "deleted"}])], ignore_index=True) 

268 return df 

269 

270 def call_application_api(self, method_name: str = None, params: dict = None) -> pd.DataFrame: 

271 """ 

272 Call Google Calendar API and map the data to pandas DataFrame 

273 Args: 

274 method_name (str): method name 

275 params (dict): query parameters 

276 Returns: 

277 DataFrame 

278 """ 

279 if method_name == "get_events": 

280 return self.get_events(params) 

281 elif method_name == "create_event": 

282 return self.create_event(params) 

283 elif method_name == "update_event": 

284 return self.update_event(params) 

285 elif method_name == "delete_event": 

286 return self.delete_event(params) 

287 else: 

288 raise NotImplementedError(f"Unknown method {method_name}")