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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +15 -0
- mindsdb/api/executor/command_executor.py +1 -1
- mindsdb/api/executor/datahub/datanodes/system_tables.py +6 -1
- mindsdb/api/executor/planner/query_planner.py +6 -2
- mindsdb/api/executor/sql_query/steps/prepare_steps.py +2 -1
- mindsdb/api/mongo/classes/query_sql.py +2 -1
- mindsdb/api/mongo/responders/aggregate.py +2 -2
- mindsdb/api/mongo/responders/coll_stats.py +3 -2
- mindsdb/api/mongo/responders/db_stats.py +2 -1
- mindsdb/api/mongo/responders/insert.py +4 -2
- mindsdb/api/mysql/mysql_proxy/classes/fake_mysql_proxy/fake_mysql_proxy.py +2 -1
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +5 -4
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +2 -4
- mindsdb/integrations/handlers/autosklearn_handler/autosklearn_handler.py +1 -1
- mindsdb/integrations/handlers/gmail_handler/connection_args.py +2 -2
- mindsdb/integrations/handlers/gmail_handler/gmail_handler.py +19 -66
- mindsdb/integrations/handlers/gmail_handler/requirements.txt +0 -1
- mindsdb/integrations/handlers/google_calendar_handler/connection_args.py +15 -0
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +31 -41
- mindsdb/integrations/handlers/google_calendar_handler/requirements.txt +0 -2
- mindsdb/integrations/handlers/youtube_handler/youtube_handler.py +2 -38
- mindsdb/integrations/libs/llm/utils.py +2 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/google/google_user_oauth_utilities.py +29 -38
- mindsdb/integrations/utilities/pydantic_utils.py +208 -0
- mindsdb/integrations/utilities/rag/pipelines/rag.py +11 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +800 -135
- mindsdb/integrations/utilities/rag/settings.py +390 -152
- mindsdb/integrations/utilities/sql_utils.py +2 -1
- mindsdb/interfaces/agents/agents_controller.py +11 -7
- mindsdb/interfaces/agents/mindsdb_chat_model.py +4 -2
- mindsdb/interfaces/chatbot/chatbot_controller.py +9 -8
- mindsdb/interfaces/database/database.py +2 -1
- mindsdb/interfaces/database/projects.py +28 -2
- mindsdb/interfaces/jobs/jobs_controller.py +4 -1
- mindsdb/interfaces/model/model_controller.py +5 -2
- mindsdb/interfaces/skills/retrieval_tool.py +128 -39
- mindsdb/interfaces/skills/skill_tool.py +7 -7
- mindsdb/interfaces/skills/skills_controller.py +8 -4
- mindsdb/interfaces/storage/db.py +14 -0
- mindsdb/interfaces/storage/json.py +59 -0
- mindsdb/interfaces/storage/model_fs.py +85 -3
- mindsdb/interfaces/triggers/triggers_controller.py +2 -1
- mindsdb/migrations/versions/2022-10-14_43c52d23845a_projects.py +17 -3
- mindsdb/migrations/versions/2025-02-14_4521dafe89ab_added_encrypted_content_to_json_storage.py +29 -0
- mindsdb/migrations/versions/2025-02-19_11347c213b36_added_metadata_to_projects.py +41 -0
- mindsdb/utilities/config.py +5 -1
- mindsdb/utilities/functions.py +11 -0
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/METADATA +221 -223
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/RECORD +53 -51
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/WHEEL +1 -1
- mindsdb/integrations/handlers/gmail_handler/utils.py +0 -45
- {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.1.0.dist-info}/LICENSE +0 -0
- {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.
|
|
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 = "
|
|
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 = [
|
|
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(
|
|
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
|
|
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 = '
|
|
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', '
|
|
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', '
|
|
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 != '
|
|
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 == '
|
|
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 == '
|
|
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",
|
|
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",
|
|
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 = '
|
|
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
|
|
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=
|
|
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
|
|
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 = '
|
|
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)
|
|
@@ -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 .
|
|
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 = [
|
|
35
|
-
|
|
36
|
-
|
|
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.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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)
|