MindsDB 25.2.4.0__py3-none-any.whl → 25.3.1.0__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.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (54) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +15 -0
  3. mindsdb/api/executor/command_executor.py +1 -1
  4. mindsdb/api/executor/datahub/datanodes/system_tables.py +6 -1
  5. mindsdb/api/executor/planner/query_planner.py +6 -2
  6. mindsdb/api/executor/sql_query/steps/prepare_steps.py +2 -1
  7. mindsdb/api/mongo/classes/query_sql.py +2 -1
  8. mindsdb/api/mongo/responders/aggregate.py +2 -2
  9. mindsdb/api/mongo/responders/coll_stats.py +3 -2
  10. mindsdb/api/mongo/responders/db_stats.py +2 -1
  11. mindsdb/api/mongo/responders/insert.py +4 -2
  12. mindsdb/api/mysql/mysql_proxy/classes/fake_mysql_proxy/fake_mysql_proxy.py +2 -1
  13. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +5 -4
  14. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +2 -4
  15. mindsdb/integrations/handlers/autosklearn_handler/autosklearn_handler.py +1 -1
  16. mindsdb/integrations/handlers/gmail_handler/connection_args.py +2 -2
  17. mindsdb/integrations/handlers/gmail_handler/gmail_handler.py +19 -66
  18. mindsdb/integrations/handlers/gmail_handler/requirements.txt +0 -1
  19. mindsdb/integrations/handlers/google_calendar_handler/connection_args.py +15 -0
  20. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +31 -41
  21. mindsdb/integrations/handlers/google_calendar_handler/requirements.txt +0 -2
  22. mindsdb/integrations/handlers/youtube_handler/youtube_handler.py +2 -38
  23. mindsdb/integrations/libs/llm/utils.py +2 -1
  24. mindsdb/integrations/utilities/handlers/auth_utilities/google/google_user_oauth_utilities.py +29 -38
  25. mindsdb/integrations/utilities/pydantic_utils.py +208 -0
  26. mindsdb/integrations/utilities/rag/pipelines/rag.py +11 -4
  27. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +800 -135
  28. mindsdb/integrations/utilities/rag/settings.py +390 -152
  29. mindsdb/integrations/utilities/sql_utils.py +2 -1
  30. mindsdb/interfaces/agents/agents_controller.py +11 -7
  31. mindsdb/interfaces/agents/mindsdb_chat_model.py +4 -2
  32. mindsdb/interfaces/chatbot/chatbot_controller.py +9 -8
  33. mindsdb/interfaces/database/database.py +2 -1
  34. mindsdb/interfaces/database/projects.py +28 -2
  35. mindsdb/interfaces/jobs/jobs_controller.py +4 -1
  36. mindsdb/interfaces/model/model_controller.py +5 -2
  37. mindsdb/interfaces/skills/retrieval_tool.py +128 -39
  38. mindsdb/interfaces/skills/skill_tool.py +7 -7
  39. mindsdb/interfaces/skills/skills_controller.py +8 -4
  40. mindsdb/interfaces/storage/db.py +14 -0
  41. mindsdb/interfaces/storage/json.py +59 -0
  42. mindsdb/interfaces/storage/model_fs.py +85 -3
  43. mindsdb/interfaces/triggers/triggers_controller.py +2 -1
  44. mindsdb/migrations/versions/2022-10-14_43c52d23845a_projects.py +17 -3
  45. mindsdb/migrations/versions/2025-02-14_4521dafe89ab_added_encrypted_content_to_json_storage.py +29 -0
  46. mindsdb/migrations/versions/2025-02-19_11347c213b36_added_metadata_to_projects.py +41 -0
  47. mindsdb/utilities/config.py +5 -1
  48. mindsdb/utilities/functions.py +11 -0
  49. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/METADATA +221 -223
  50. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/RECORD +53 -51
  51. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/WHEEL +1 -1
  52. mindsdb/integrations/handlers/gmail_handler/utils.py +0 -45
  53. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/LICENSE +0 -0
  54. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  __title__ = 'MindsDB'
2
2
  __package_name__ = 'mindsdb'
3
- __version__ = '25.2.4.0'
3
+ __version__ = '25.3.1.0'
4
4
  __description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
5
5
  __email__ = "jorge@mindsdb.com"
6
6
  __author__ = 'MindsDB Inc'
mindsdb/__main__.py CHANGED
@@ -22,12 +22,14 @@ logger.debug("Starting MindsDB...")
22
22
 
23
23
  from mindsdb.__about__ import __version__ as mindsdb_version
24
24
  from mindsdb.utilities.config import config
25
+ from mindsdb.utilities.exception import EntityNotExistsError
25
26
  from mindsdb.utilities.starters import (
26
27
  start_http, start_mysql, start_mongo, start_postgres, start_ml_task_queue, start_scheduler, start_tasks
27
28
  )
28
29
  from mindsdb.utilities.ps import is_pid_listen_port, get_child_pids
29
30
  from mindsdb.utilities.functions import get_versions_where_predictors_become_obsolete
30
31
  from mindsdb.interfaces.database.integrations import integration_controller
32
+ from mindsdb.interfaces.database.projects import ProjectController
31
33
  import mindsdb.interfaces.storage.db as db
32
34
  from mindsdb.integrations.utilities.install import install_dependencies
33
35
  from mindsdb.utilities.fs import clean_process_marks, clean_unlinked_process_marks
@@ -293,6 +295,19 @@ if __name__ == '__main__':
293
295
  except Exception as e:
294
296
  logger.error(f"Error! Something went wrong during DB migrations: {e}")
295
297
 
298
+ logger.debug(f"Checking if default project {config.get('default_project')} exists")
299
+ project_controller = ProjectController()
300
+
301
+ current_default_project = project_controller.get(is_default=True)
302
+ if current_default_project.record.name != config.get('default_project'):
303
+ try:
304
+ new_default_project = project_controller.get(name=config.get('default_project'))
305
+ log.critical(f"A project with the name '{config.get('default_project')}' already exists")
306
+ sys.exit(1)
307
+ except EntityNotExistsError:
308
+ pass
309
+ project_controller.update(current_default_project.record.id, new_name=config.get('default_project'))
310
+
296
311
  apis = os.getenv('MINDSDB_APIS') or config.cmd_args.api
297
312
 
298
313
  if apis is None: # If "--api" option is not specified, start the default APIs
@@ -1549,7 +1549,7 @@ class ExecuteCommands:
1549
1549
  elif isinstance(database_name, str) and len(database_name) > 0:
1550
1550
  db = database_name
1551
1551
  else:
1552
- db = "mindsdb"
1552
+ db = self.session.config.get("default_project")
1553
1553
  table_name = target.parts[-1]
1554
1554
 
1555
1555
  new_where = BinaryOperation(
@@ -8,6 +8,7 @@ from mindsdb.api.executor.datahub.classes.tables_row import (
8
8
  TablesRow,
9
9
  )
10
10
  from mindsdb.utilities import log
11
+ from mindsdb.utilities.config import config
11
12
 
12
13
  logger = log.getLogger(__name__)
13
14
 
@@ -292,7 +293,11 @@ class ColumnsTable(Table):
292
293
  databases, tables_names = _get_scope(query)
293
294
 
294
295
  if databases is None:
295
- databases = ['information_schema', 'mindsdb', 'files']
296
+ databases = [
297
+ 'information_schema',
298
+ config.get('default_project'),
299
+ 'files'
300
+ ]
296
301
 
297
302
  for db_name in databases:
298
303
  tables = {}
@@ -25,6 +25,10 @@ from mindsdb.api.executor.planner.utils import (
25
25
  )
26
26
  from mindsdb.api.executor.planner.plan_join import PlanJoin
27
27
  from mindsdb.api.executor.planner.query_prepare import PreparedStatementPlanner
28
+ from mindsdb.utilities.config import config
29
+
30
+
31
+ default_project = config.get('default_project')
28
32
 
29
33
 
30
34
  class QueryPlanner:
@@ -54,12 +58,12 @@ class QueryPlanner:
54
58
  self.integrations[integration_name] = integration
55
59
 
56
60
  # allow to select from mindsdb namespace
57
- _projects.add('mindsdb')
61
+ _projects.add(default_project)
58
62
 
59
63
  self.default_namespace = default_namespace
60
64
 
61
65
  # legacy parameter
62
- self.predictor_namespace = predictor_namespace.lower() if predictor_namespace else 'mindsdb'
66
+ self.predictor_namespace = predictor_namespace.lower() if predictor_namespace else default_project
63
67
 
64
68
  # map for lower names of predictors
65
69
 
@@ -10,6 +10,7 @@ from mindsdb.api.executor.planner.steps import (
10
10
  )
11
11
 
12
12
  from mindsdb.api.executor.sql_query.result_set import ResultSet, Column
13
+ from mindsdb.utilities.config import config
13
14
 
14
15
  from .base import BaseStepCall
15
16
 
@@ -20,7 +21,7 @@ class GetPredictorColumnsCall(BaseStepCall):
20
21
 
21
22
  def call(self, step):
22
23
 
23
- mindsdb_database_name = 'mindsdb'
24
+ mindsdb_database_name = config.get('default_project')
24
25
 
25
26
  predictor_name = step.predictor.parts[-1]
26
27
  dn = self.session.datahub.get(mindsdb_database_name)
@@ -1,10 +1,11 @@
1
1
  from mindsdb.api.executor.controllers import SessionController
2
2
  from mindsdb.api.executor.command_executor import ExecuteCommands
3
+ from mindsdb.utilities.config import config
3
4
 
4
5
 
5
6
  def run_sql_command(request_env, ast_query):
6
7
  sql_session = SessionController()
7
- sql_session.database = request_env.get('database', 'mindsdb')
8
+ sql_session.database = request_env.get('database', config.get('default_project'))
8
9
 
9
10
  command_executor = ExecuteCommands(sql_session)
10
11
  ret = command_executor.execute_command(ast_query)
@@ -5,8 +5,8 @@ from mindsdb_sql_parser.ast import Identifier, Insert, CreateTable
5
5
  from mindsdb.api.mongo.classes import Responder
6
6
  import mindsdb.api.mongo.functions as helpers
7
7
  from mindsdb.api.mongo.responders.find import find_to_ast
8
-
9
8
  from mindsdb.api.mongo.classes.query_sql import run_sql_command
9
+ from mindsdb.utilities.config import config
10
10
 
11
11
 
12
12
  def aggregate_to_ast(query, database):
@@ -63,7 +63,7 @@ class Responce(Responder):
63
63
 
64
64
  first_step = query['pipeline'][0]
65
65
  if '$match' in first_step:
66
- ast_query = aggregate_to_ast(query, request_env.get('database', 'mindsdb'))
66
+ ast_query = aggregate_to_ast(query, request_env.get('database', config.get('default_project')))
67
67
 
68
68
  data = run_sql_command(request_env, ast_query)
69
69
 
@@ -3,6 +3,7 @@ from mindsdb_sql_parser.ast import Describe, Identifier
3
3
  from mindsdb.api.mongo.classes import Responder
4
4
  import mindsdb.api.mongo.functions as helpers
5
5
  from mindsdb.api.mongo.classes.query_sql import run_sql_command
6
+ from mindsdb.utilities.config import config
6
7
 
7
8
 
8
9
  class Responce(Responder):
@@ -14,7 +15,7 @@ class Responce(Responder):
14
15
 
15
16
  scale = query.get('scale')
16
17
 
17
- if db != 'mindsdb' or collection == 'predictors' or scale is None:
18
+ if db != config.get('default_project') or collection == 'predictors' or scale is None:
18
19
  # old behavior
19
20
  # NOTE real answer is huge, i removed most data from it.
20
21
  res = {
@@ -37,7 +38,7 @@ class Responce(Responder):
37
38
  }
38
39
 
39
40
  res['ns'] = f"{db}.{collection}"
40
- if db == 'mindsdb' and collection == 'predictors':
41
+ if db == config.get('default_project') and collection == 'predictors':
41
42
  res['count'] = len(mindsdb_env['model_controller'].get_models())
42
43
  else:
43
44
 
@@ -1,5 +1,6 @@
1
1
  from mindsdb.api.mongo.classes import Responder
2
2
  import mindsdb.api.mongo.functions as helpers
3
+ from mindsdb.utilities.config import config
3
4
 
4
5
 
5
6
  class Responce(Responder):
@@ -8,7 +9,7 @@ class Responce(Responder):
8
9
  def result(self, query, request_env, mindsdb_env, session):
9
10
  db = query['$db']
10
11
  collections = 0
11
- if db == 'mindsdb':
12
+ if db == config.get('default_project'):
12
13
  collections = 2 + len(mindsdb_env['model_controller'].get_models())
13
14
  return {
14
15
  'db': db,
@@ -16,8 +16,10 @@ from mindsdb.api.mongo.responders.find import find_to_ast
16
16
  from mindsdb.api.mongo.utilities.mongodb_parser import MongodbParser
17
17
  from mindsdb.integrations.libs.response import HandlerStatusResponse
18
18
  from mindsdb.utilities import log
19
+ from mindsdb.utilities.config import config
19
20
 
20
21
  logger = log.getLogger(__name__)
22
+ default_project = config.get("default_project")
21
23
 
22
24
 
23
25
  class Responce(Responder):
@@ -186,7 +188,7 @@ class Responce(Responder):
186
188
  query["sort"] = step["args"][0]
187
189
  # TODO implement group modifiers
188
190
  ast_query = find_to_ast(
189
- query, request_env.get("database", "mindsdb")
191
+ query, request_env.get("database", default_project)
190
192
  )
191
193
 
192
194
  # to string
@@ -197,7 +199,7 @@ class Responce(Responder):
197
199
  "pipeline": mql.pipeline[0]["args"][0],
198
200
  }
199
201
  ast_query = aggregate_to_ast(
200
- query, request_env.get("database", "mindsdb")
202
+ query, request_env.get("database", default_project)
201
203
  )
202
204
  query_str = ast_query.to_string()
203
205
  except Exception:
@@ -1,6 +1,7 @@
1
1
  from mindsdb.api.executor.controllers import SessionController
2
2
  from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import CHARSET_NUMBERS
3
3
  from mindsdb.api.mysql.mysql_proxy.mysql_proxy import MysqlProxy
4
+ from mindsdb.utilities.config import config
4
5
 
5
6
 
6
7
  def empty_fn():
@@ -29,7 +30,7 @@ class FakeMysqlProxy(MysqlProxy):
29
30
  self.connection_id = None
30
31
 
31
32
  self.session = SessionController()
32
- self.session.database = 'mindsdb'
33
+ self.session.database = config.get('default_project')
33
34
 
34
35
  def is_cloud_connection(self):
35
36
  return {
@@ -81,7 +81,7 @@ from mindsdb.api.executor import exceptions as exec_exc
81
81
  from mindsdb.api.common.check_auth import check_auth
82
82
  from mindsdb.api.mysql.mysql_proxy.utilities.lightwood_dtype import dtype
83
83
  from mindsdb.utilities import log
84
- from mindsdb.utilities.config import Config
84
+ from mindsdb.utilities.config import config
85
85
  from mindsdb.utilities.context import context as ctx
86
86
  from mindsdb.utilities.otel.metric_handlers import get_query_request_counter
87
87
  from mindsdb.utilities.wizards import make_ssl_cert
@@ -448,7 +448,6 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
448
448
  connection. '0000' selected because in real mysql connection it should be lenght of package,
449
449
  and it can not be 0.
450
450
  """
451
- config = Config()
452
451
  is_cloud = config.get("cloud", False)
453
452
 
454
453
  if sys.platform != "linux" or is_cloud is False:
@@ -746,7 +745,7 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
746
745
  sql = SqlStatementParser.clear_sql(sql)
747
746
  logger.debug(f'Incoming query: {sql}')
748
747
  profiler.set_meta(
749
- query=sql, api="mysql", environment=Config().get("environment")
748
+ query=sql, api="mysql", environment=config.get("environment")
750
749
  )
751
750
  with profiler.Context("mysql_query_processing"):
752
751
  response = self.process_query(sql)
@@ -877,6 +876,9 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
877
876
  def set_context(self, context):
878
877
  if "db" in context:
879
878
  self.session.database = context["db"]
879
+ else:
880
+ self.session.database = config.get('default_project')
881
+
880
882
  if "profiling" in context:
881
883
  self.session.profiling = context["profiling"]
882
884
  if "predictor_cache" in context:
@@ -903,7 +905,6 @@ class MysqlProxy(SocketServer.BaseRequestHandler):
903
905
  Create a server and wait for incoming connections until Ctrl-C
904
906
  """
905
907
  global logger
906
- config = Config()
907
908
 
908
909
  cert_path = config["api"]["mysql"].get("certificate_path")
909
910
  if cert_path is None or cert_path == "":
@@ -26,7 +26,7 @@ from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_message impor
26
26
  from mindsdb.api.postgres.postgres_proxy.postgres_packets.postgres_packets import PostgresPacketReader, \
27
27
  PostgresPacketBuilder
28
28
  from mindsdb.api.postgres.postgres_proxy.utilities import strip_null_byte
29
- from mindsdb.utilities.config import Config
29
+ from mindsdb.utilities.config import config
30
30
  from mindsdb.utilities.context import context as ctx
31
31
  from mindsdb.utilities import log
32
32
  from mindsdb.api.mysql.mysql_proxy.external_libs.mysql_scramble import scramble as scramble_func
@@ -92,7 +92,6 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
92
92
  Copied from mysql_proxy
93
93
  TODO: Extract method into common
94
94
  """
95
- config = Config()
96
95
  is_cloud = config.get('cloud', False)
97
96
 
98
97
  if sys.platform != 'linux' or is_cloud is False:
@@ -237,7 +236,7 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
237
236
  self.server.connection_id += 1
238
237
  self.connection_id = self.server.connection_id
239
238
  self.session = SessionController()
240
- self.session.database = 'mindsdb'
239
+ self.session.database = config.get('default_project')
241
240
 
242
241
  if hasattr(self.server, 'salt') and isinstance(self.server.salt, str):
243
242
  self.salt = self.server.salt
@@ -461,7 +460,6 @@ class PostgresProxyHandler(socketserver.StreamRequestHandler):
461
460
 
462
461
  @staticmethod
463
462
  def startProxy():
464
- config = Config()
465
463
  host = config['api']['postgres']['host']
466
464
  port = int(config['api']['postgres']['port'])
467
465
  server = TcpServer((host, port), PostgresProxyHandler)
@@ -50,4 +50,4 @@ class AutoSklearnHandler(BaseMLEngine):
50
50
  args = self.model_storage.json_get('args')
51
51
  df[args['target']] = predictions
52
52
 
53
- return df
53
+ return df
@@ -21,7 +21,7 @@ connection_args = OrderedDict(
21
21
  },
22
22
  code={
23
23
  'type': ARG_TYPE.STR,
24
- 'description': 'code after authorisation',
25
- 'label': 'code after authorisation',
24
+ 'description': 'Code After Authorisation',
25
+ 'label': 'Code After Authorisation',
26
26
  },
27
27
  )
@@ -1,7 +1,4 @@
1
1
  import json
2
- from shutil import copyfile
3
-
4
- import requests
5
2
 
6
3
  from mindsdb.integrations.libs.response import (
7
4
  HandlerStatusResponse as StatusResponse,
@@ -15,31 +12,28 @@ from mindsdb.utilities import log
15
12
  from mindsdb_sql_parser import parse_sql
16
13
  from mindsdb.utilities.config import Config
17
14
 
18
- import os
19
15
  import time
20
16
  from typing import List
21
17
  import pandas as pd
22
18
 
23
- from google.auth.transport.requests import Request
24
- from google.oauth2.credentials import Credentials
25
- from google_auth_oauthlib.flow import Flow
26
19
  from googleapiclient.discovery import build
27
20
  from googleapiclient.errors import HttpError
28
21
  from email.message import EmailMessage
29
22
 
30
23
  from base64 import urlsafe_b64encode, urlsafe_b64decode
31
24
 
32
- from .utils import AuthException, google_auth_flow, save_creds_to_file
25
+ from mindsdb.integrations.utilities.handlers.auth_utilities import GoogleUserOAuth2Manager
26
+ from mindsdb.integrations.utilities.handlers.auth_utilities.exceptions import AuthException
33
27
 
34
- DEFAULT_SCOPES = ['https://www.googleapis.com/auth/gmail.compose',
35
- 'https://www.googleapis.com/auth/gmail.readonly',
36
- 'https://www.googleapis.com/auth/gmail.modify']
28
+ DEFAULT_SCOPES = [
29
+ 'https://www.googleapis.com/auth/gmail.compose',
30
+ 'https://www.googleapis.com/auth/gmail.readonly',
31
+ 'https://www.googleapis.com/auth/gmail.modify'
32
+ ]
37
33
 
38
34
  logger = log.getLogger(__name__)
39
35
 
40
36
 
41
-
42
-
43
37
  class EmailsTable(APITable):
44
38
  """Implementation for the emails table for Gmail"""
45
39
 
@@ -283,6 +277,14 @@ class GmailHandler(APIHandler):
283
277
  super().__init__(name)
284
278
  self.connection_args = kwargs.get('connection_data', {})
285
279
 
280
+ self.token_file = None
281
+ self.max_page_size = 500
282
+ self.max_batch_size = 100
283
+ self.service = None
284
+ self.is_connected = False
285
+
286
+ self.handler_storage = kwargs['handler_storage']
287
+
286
288
  self.credentials_url = self.connection_args.get('credentials_url', None)
287
289
  self.credentials_file = self.connection_args.get('credentials_file', None)
288
290
  if self.connection_args.get('credentials'):
@@ -298,63 +300,11 @@ class GmailHandler(APIHandler):
298
300
  self.credentials_url = secret_url
299
301
 
300
302
  self.scopes = self.connection_args.get('scopes', DEFAULT_SCOPES)
301
- self.token_file = None
302
- self.max_page_size = 500
303
- self.max_batch_size = 100
304
- self.service = None
305
- self.is_connected = False
306
-
307
- self.handler_storage = kwargs['handler_storage']
308
303
 
309
304
  emails = EmailsTable(self)
310
305
  self.emails = emails
311
306
  self._register_table('emails', emails)
312
307
 
313
- def _download_secret_file(self, secret_file):
314
- # Giving more priority to the S3 file
315
- if self.credentials_url:
316
- response = requests.get(self.credentials_url)
317
- if response.status_code == 200:
318
- with open(secret_file, 'w') as creds:
319
- creds.write(response.text)
320
- return True
321
- else:
322
- logger.error("Failed to get credentials from S3", response.status_code)
323
-
324
- if self.credentials_file and os.path.isfile(self.credentials_file):
325
- copyfile(self.credentials_file, secret_file)
326
- return True
327
- return False
328
-
329
- def create_connection(self):
330
- creds = None
331
-
332
- # Get the current dir, we'll check for Token & Creds files in this dir
333
- curr_dir = self.handler_storage.folder_get('config')
334
-
335
- creds_file = os.path.join(curr_dir, 'creds.json')
336
- secret_file = os.path.join(curr_dir, 'secret.json')
337
-
338
- if os.path.isfile(creds_file):
339
- creds = Credentials.from_authorized_user_file(creds_file, self.scopes)
340
-
341
- if not creds or not creds.valid:
342
- if creds and creds.expired and creds.refresh_token:
343
- creds.refresh(Request())
344
-
345
- if self._download_secret_file(secret_file):
346
- # save to storage
347
- self.handler_storage.folder_sync('config')
348
- else:
349
- raise ValueError('No valid Gmail Credentials filepath or S3 url found.')
350
-
351
- creds = google_auth_flow(secret_file, self.scopes, self.connection_args.get('code'))
352
-
353
- save_creds_to_file(creds, creds_file)
354
- self.handler_storage.folder_sync('config')
355
-
356
- return build('gmail', 'v1', credentials=creds)
357
-
358
308
  def connect(self):
359
309
  """Authenticate with the Gmail API using the credentials file.
360
310
 
@@ -366,7 +316,10 @@ class GmailHandler(APIHandler):
366
316
  if self.is_connected and self.service is not None:
367
317
  return self.service
368
318
 
369
- self.service = self.create_connection()
319
+ google_oauth2_manager = GoogleUserOAuth2Manager(self.handler_storage, self.scopes, self.credentials_file, self.credentials_url, self.connection_args.get('code'))
320
+ creds = google_oauth2_manager.get_oauth2_credentials()
321
+
322
+ self.service = build('gmail', 'v1', credentials=creds)
370
323
 
371
324
  self.is_connected = True
372
325
  return self.service
@@ -1,2 +1 @@
1
1
  google-api-python-client
2
- google-auth-httplib2
@@ -4,9 +4,24 @@ from mindsdb.integrations.libs.const import HANDLER_CONNECTION_ARG_TYPE as ARG_T
4
4
 
5
5
 
6
6
  connection_args = OrderedDict(
7
+ credentials_url={
8
+ 'type': ARG_TYPE.STR,
9
+ 'description': 'URL to Service Account Keys',
10
+ 'label': 'URL to Service Account Keys',
11
+ },
12
+ credentials_file={
13
+ 'type': ARG_TYPE.STR,
14
+ 'description': 'Location of Service Account Keys',
15
+ 'label': 'Path to Service Account Keys',
16
+ },
7
17
  credentials={
8
18
  'type': ARG_TYPE.PATH,
9
19
  'description': 'Service Account Keys',
10
20
  'label': 'Upload Service Account Keys',
11
21
  },
22
+ code={
23
+ 'type': ARG_TYPE.STR,
24
+ 'description': 'Code After Authorisation',
25
+ 'label': 'Code After Authorisation',
26
+ },
12
27
  )
@@ -1,8 +1,4 @@
1
- import os
2
-
3
1
  import pandas as pd
4
- from google.auth.transport.requests import Request
5
- from google.oauth2.credentials import Credentials
6
2
  from googleapiclient.discovery import build
7
3
 
8
4
  from mindsdb.api.executor.data_types.response_type import RESPONSE_TYPE
@@ -11,13 +7,21 @@ from mindsdb.integrations.libs.response import (
11
7
  HandlerStatusResponse as StatusResponse,
12
8
  HandlerResponse as Response,
13
9
  )
10
+ from mindsdb.utilities.config import Config
14
11
  from mindsdb.utilities import log
15
- logger = log.getLogger(__name__)
16
-
17
- from mindsdb.integrations.handlers.gmail_handler.utils import AuthException, google_auth_flow, save_creds_to_file
12
+ from mindsdb.integrations.utilities.handlers.auth_utilities import GoogleUserOAuth2Manager
13
+ from mindsdb.integrations.utilities.handlers.auth_utilities.exceptions import AuthException
18
14
 
19
15
  from .google_calendar_tables import GoogleCalendarEventsTable
20
16
 
17
+ DEFAULT_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
+
23
+ logger = log.getLogger(__name__)
24
+
21
25
 
22
26
  class GoogleCalendarHandler(APIHandler):
23
27
  """
@@ -35,20 +39,29 @@ class GoogleCalendarHandler(APIHandler):
35
39
  events (GoogleCalendarEventsTable): The `GoogleCalendarEventsTable` object for interacting with the events table.
36
40
  """
37
41
  super().__init__(name)
42
+ self.connection_data = kwargs.get('connection_data', {})
38
43
 
39
- self.token = None
40
44
  self.service = None
41
- self.connection_data = kwargs.get('connection_data', {})
42
- self.credentials_file = self.connection_data['credentials']
43
- self.scopes = [
44
- 'https://www.googleapis.com/auth/calendar',
45
- 'https://www.googleapis.com/auth/calendar.events',
46
- 'https://www.googleapis.com/auth/calendar.readonly'
47
- ]
48
45
  self.is_connected = False
49
46
 
50
47
  self.handler_storage = kwargs['handler_storage']
51
48
 
49
+ self.credentials_url = self.connection_data.get('credentials_url', None)
50
+ self.credentials_file = self.connection_data.get('credentials_file', None)
51
+ if self.connection_data.get('credentials'):
52
+ self.credentials_file = self.connection_data.pop('credentials')
53
+ if not self.credentials_file and not self.credentials_url:
54
+ # try to get from config
55
+ gcalendar_config = Config().get('handlers', {}).get('youtube', {})
56
+ secret_file = gcalendar_config.get('credentials_file')
57
+ secret_url = gcalendar_config.get('credentials_url')
58
+ if secret_file:
59
+ self.credentials_file = secret_file
60
+ elif secret_url:
61
+ self.credentials_url = secret_url
62
+
63
+ self.scopes = self.connection_data.get('scopes', DEFAULT_SCOPES)
64
+
52
65
  events = GoogleCalendarEventsTable(self)
53
66
  self.events = events
54
67
  self._register_table('events', events)
@@ -64,32 +77,8 @@ class GoogleCalendarHandler(APIHandler):
64
77
  if self.is_connected is True:
65
78
  return self.service
66
79
 
67
- secret_file = self.credentials_file
68
-
69
- curr_dir = self.handler_storage.folder_get('config')
70
-
71
- creds_file = None
72
- try:
73
- creds_file = os.path.join(curr_dir, 'secret.json')
74
- except Exception:
75
- pass
76
-
77
- creds = None
78
- if os.path.isfile(creds_file):
79
- creds = Credentials.from_authorized_user_file(creds_file, self.scopes)
80
-
81
- if not creds or not creds.valid:
82
- if creds and creds.expired and creds.refresh_token:
83
- creds.refresh(Request())
84
-
85
- save_creds_to_file(creds, creds_file)
86
- self.handler_storage.folder_sync('config')
87
-
88
- else:
89
- creds = google_auth_flow(secret_file, self.scopes, self.connection_data.get('code'))
90
-
91
- save_creds_to_file(creds, creds_file)
92
- self.handler_storage.folder_sync('config')
80
+ google_oauth2_manager = GoogleUserOAuth2Manager(self.handler_storage, self.scopes, self.credentials_file, self.credentials_url, self.connection_data.get('code'))
81
+ creds = google_oauth2_manager.get_oauth2_credentials()
93
82
 
94
83
  self.service = build('calendar', 'v3', credentials=creds)
95
84
  return self.service
@@ -106,6 +95,7 @@ class GoogleCalendarHandler(APIHandler):
106
95
  try:
107
96
  self.connect()
108
97
  response.success = True
98
+ response.copy_storage = True
109
99
 
110
100
  except AuthException as error:
111
101
  response.error_message = str(error)
@@ -1,3 +1 @@
1
1
  google-api-python-client
2
- google-auth-httplib2
3
- -r mindsdb/integrations/handlers/gmail_handler/requirements.txt