MindsDB 25.9.1.2__py3-none-any.whl → 25.9.3rc1__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 +39 -20
- mindsdb/api/a2a/agent.py +7 -9
- mindsdb/api/a2a/common/server/server.py +3 -3
- mindsdb/api/a2a/common/server/task_manager.py +4 -4
- mindsdb/api/a2a/task_manager.py +15 -17
- mindsdb/api/common/middleware.py +9 -11
- mindsdb/api/executor/command_executor.py +2 -4
- mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +100 -48
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
- mindsdb/api/executor/datahub/datanodes/system_tables.py +1 -1
- mindsdb/api/executor/exceptions.py +29 -10
- mindsdb/api/executor/planner/plan_join.py +17 -3
- mindsdb/api/executor/sql_query/sql_query.py +74 -74
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
- mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
- mindsdb/api/executor/utilities/functions.py +6 -6
- mindsdb/api/executor/utilities/sql.py +32 -16
- mindsdb/api/http/gui.py +5 -11
- mindsdb/api/http/initialize.py +8 -10
- mindsdb/api/http/namespaces/agents.py +10 -12
- mindsdb/api/http/namespaces/analysis.py +13 -20
- mindsdb/api/http/namespaces/auth.py +1 -1
- mindsdb/api/http/namespaces/config.py +15 -11
- mindsdb/api/http/namespaces/databases.py +140 -201
- mindsdb/api/http/namespaces/file.py +15 -4
- mindsdb/api/http/namespaces/handlers.py +7 -2
- mindsdb/api/http/namespaces/knowledge_bases.py +8 -7
- mindsdb/api/http/namespaces/models.py +94 -126
- mindsdb/api/http/namespaces/projects.py +13 -22
- mindsdb/api/http/namespaces/sql.py +33 -25
- mindsdb/api/http/namespaces/tab.py +27 -37
- mindsdb/api/http/namespaces/views.py +1 -1
- mindsdb/api/http/start.py +14 -8
- mindsdb/api/mcp/__init__.py +2 -1
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
- mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +6 -13
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +40 -28
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +168 -185
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +11 -5
- mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +1 -1
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +20 -2
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +18 -3
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
- mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
- mindsdb/integrations/libs/api_handler.py +10 -10
- mindsdb/integrations/libs/base.py +4 -4
- mindsdb/integrations/libs/llm/utils.py +2 -2
- mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
- mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
- mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
- mindsdb/integrations/libs/process_cache.py +132 -140
- mindsdb/integrations/libs/response.py +18 -12
- mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
- mindsdb/integrations/utilities/files/file_reader.py +6 -7
- mindsdb/integrations/utilities/rag/config_loader.py +37 -26
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +59 -9
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
- mindsdb/integrations/utilities/rag/settings.py +58 -133
- mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
- mindsdb/interfaces/agents/agents_controller.py +2 -1
- mindsdb/interfaces/agents/constants.py +0 -2
- mindsdb/interfaces/agents/litellm_server.py +34 -58
- mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
- mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
- mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
- mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
- mindsdb/interfaces/chatbot/polling.py +30 -18
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +10 -10
- mindsdb/interfaces/database/integrations.py +19 -2
- mindsdb/interfaces/file/file_controller.py +6 -6
- mindsdb/interfaces/functions/controller.py +1 -1
- mindsdb/interfaces/functions/to_markdown.py +2 -2
- mindsdb/interfaces/jobs/jobs_controller.py +5 -5
- mindsdb/interfaces/jobs/scheduler.py +3 -8
- mindsdb/interfaces/knowledge_base/controller.py +54 -25
- mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
- mindsdb/interfaces/model/model_controller.py +170 -166
- mindsdb/interfaces/query_context/context_controller.py +14 -2
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +6 -4
- mindsdb/interfaces/skills/retrieval_tool.py +43 -50
- mindsdb/interfaces/skills/skill_tool.py +2 -2
- mindsdb/interfaces/skills/sql_agent.py +25 -19
- mindsdb/interfaces/storage/fs.py +114 -169
- mindsdb/interfaces/storage/json.py +19 -18
- mindsdb/interfaces/storage/model_fs.py +54 -92
- mindsdb/interfaces/tabs/tabs_controller.py +49 -72
- mindsdb/interfaces/tasks/task_monitor.py +3 -9
- mindsdb/interfaces/tasks/task_thread.py +7 -9
- mindsdb/interfaces/triggers/trigger_task.py +7 -13
- mindsdb/interfaces/triggers/triggers_controller.py +47 -50
- mindsdb/migrations/migrate.py +16 -16
- mindsdb/utilities/api_status.py +58 -0
- mindsdb/utilities/config.py +49 -0
- mindsdb/utilities/exception.py +40 -1
- mindsdb/utilities/fs.py +0 -1
- mindsdb/utilities/hooks/profiling.py +17 -14
- mindsdb/utilities/langfuse.py +40 -45
- mindsdb/utilities/log.py +272 -0
- mindsdb/utilities/ml_task_queue/consumer.py +52 -58
- mindsdb/utilities/ml_task_queue/producer.py +26 -30
- mindsdb/utilities/render/sqlalchemy_render.py +8 -7
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/METADATA +266 -261
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/RECORD +119 -119
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/top_level.txt +0 -0
|
@@ -16,7 +16,6 @@ logger = log.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TaskMonitor:
|
|
19
|
-
|
|
20
19
|
MONITOR_INTERVAL_SECONDS = 2
|
|
21
20
|
LOCK_EXPIRED_SECONDS = MONITOR_INTERVAL_SECONDS * 30
|
|
22
21
|
|
|
@@ -39,15 +38,14 @@ class TaskMonitor:
|
|
|
39
38
|
self.stop_all_tasks()
|
|
40
39
|
return
|
|
41
40
|
|
|
42
|
-
except Exception
|
|
43
|
-
logger.
|
|
41
|
+
except Exception:
|
|
42
|
+
logger.exception("Error in TaskMonitor.start")
|
|
44
43
|
db.session.rollback()
|
|
45
44
|
|
|
46
45
|
if stop_event is not None and stop_event.is_set():
|
|
47
46
|
return
|
|
48
47
|
|
|
49
48
|
def stop_all_tasks(self):
|
|
50
|
-
|
|
51
49
|
active_tasks = list(self._active_tasks.keys())
|
|
52
50
|
for task_id in active_tasks:
|
|
53
51
|
self.stop_task(task_id)
|
|
@@ -65,7 +63,6 @@ class TaskMonitor:
|
|
|
65
63
|
# Check active tasks
|
|
66
64
|
active_tasks = list(self._active_tasks.items())
|
|
67
65
|
for task_id, task in active_tasks:
|
|
68
|
-
|
|
69
66
|
if task_id not in allowed_tasks:
|
|
70
67
|
# old task
|
|
71
68
|
self.stop_task(task_id)
|
|
@@ -96,9 +93,7 @@ class TaskMonitor:
|
|
|
96
93
|
task.run_by = run_by
|
|
97
94
|
task.alive_time = db_date
|
|
98
95
|
|
|
99
|
-
elif db_date - task.alive_time > dt.timedelta(
|
|
100
|
-
seconds=self.LOCK_EXPIRED_SECONDS
|
|
101
|
-
):
|
|
96
|
+
elif db_date - task.alive_time > dt.timedelta(seconds=self.LOCK_EXPIRED_SECONDS):
|
|
102
97
|
# lock expired
|
|
103
98
|
task.run_by = run_by
|
|
104
99
|
task.alive_time = db_date
|
|
@@ -145,7 +140,6 @@ class TaskMonitor:
|
|
|
145
140
|
|
|
146
141
|
|
|
147
142
|
def start(verbose=False):
|
|
148
|
-
|
|
149
143
|
monitor = TaskMonitor()
|
|
150
144
|
monitor.start()
|
|
151
145
|
|
|
@@ -12,7 +12,6 @@ logger = log.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class TaskThread(threading.Thread):
|
|
15
|
-
|
|
16
15
|
def __init__(self, task_id):
|
|
17
16
|
threading.Thread.__init__(self)
|
|
18
17
|
self.task_id = task_id
|
|
@@ -34,28 +33,27 @@ class TaskThread(threading.Thread):
|
|
|
34
33
|
self.object_type = task_record.object_type
|
|
35
34
|
self.object_id = task_record.object_id
|
|
36
35
|
|
|
37
|
-
logger.info(f
|
|
36
|
+
logger.info(f"Task starting: {self.object_type}.{self.object_id}")
|
|
38
37
|
try:
|
|
39
|
-
if self.object_type ==
|
|
40
|
-
|
|
38
|
+
if self.object_type == "trigger":
|
|
41
39
|
trigger = TriggerTask(self.task_id, self.object_id)
|
|
42
40
|
trigger.run(self._stop_event)
|
|
43
41
|
|
|
44
|
-
elif self.object_type ==
|
|
42
|
+
elif self.object_type == "chatbot":
|
|
45
43
|
bot = ChatBotTask(self.task_id, self.object_id)
|
|
46
44
|
bot.run(self._stop_event)
|
|
47
45
|
|
|
48
|
-
elif self.object_type ==
|
|
46
|
+
elif self.object_type == "query":
|
|
49
47
|
query = QueryTask(self.task_id, self.object_id)
|
|
50
48
|
query.run(self._stop_event)
|
|
51
49
|
|
|
52
50
|
except Exception:
|
|
53
|
-
logger.
|
|
54
|
-
task_record.last_error =
|
|
51
|
+
logger.exception("Error during task processing:")
|
|
52
|
+
task_record.last_error = traceback.format_exc()
|
|
55
53
|
|
|
56
54
|
db.session.commit()
|
|
57
55
|
|
|
58
56
|
def stop(self):
|
|
59
|
-
logger.info(f
|
|
57
|
+
logger.info(f"Task stopping: {self.object_type}.{self.object_id}")
|
|
60
58
|
|
|
61
59
|
self._stop_event.set()
|
|
@@ -44,19 +44,19 @@ class TriggerTask(BaseTask):
|
|
|
44
44
|
|
|
45
45
|
# subscribe
|
|
46
46
|
database = session.integration_controller.get_by_id(trigger.database_id)
|
|
47
|
-
data_handler = session.integration_controller.get_data_handler(database[
|
|
47
|
+
data_handler = session.integration_controller.get_data_handler(database["name"])
|
|
48
48
|
|
|
49
49
|
columns = trigger.columns
|
|
50
50
|
if columns is not None:
|
|
51
|
-
if columns ==
|
|
51
|
+
if columns == "":
|
|
52
52
|
columns = None
|
|
53
53
|
else:
|
|
54
|
-
columns = columns.split(
|
|
54
|
+
columns = columns.split("|")
|
|
55
55
|
|
|
56
56
|
data_handler.subscribe(stop_event, self._callback, trigger.table_name, columns=columns)
|
|
57
57
|
|
|
58
58
|
def _callback(self, row, key=None):
|
|
59
|
-
logger.debug(f
|
|
59
|
+
logger.debug(f"trigger call: {row}, {key}")
|
|
60
60
|
|
|
61
61
|
# set up environment
|
|
62
62
|
ctx.load(self._ctx_dump)
|
|
@@ -64,21 +64,14 @@ class TriggerTask(BaseTask):
|
|
|
64
64
|
try:
|
|
65
65
|
if key is not None:
|
|
66
66
|
row.update(key)
|
|
67
|
-
table = [
|
|
68
|
-
row
|
|
69
|
-
]
|
|
67
|
+
table = [row]
|
|
70
68
|
|
|
71
69
|
# inject data to query
|
|
72
70
|
query = copy.deepcopy(self.query)
|
|
73
71
|
|
|
74
72
|
def find_table(node, is_table, **kwargs):
|
|
75
|
-
|
|
76
73
|
if is_table:
|
|
77
|
-
if (
|
|
78
|
-
isinstance(node, Identifier)
|
|
79
|
-
and len(node.parts) == 1
|
|
80
|
-
and node.parts[0] == 'TABLE_DELTA'
|
|
81
|
-
):
|
|
74
|
+
if isinstance(node, Identifier) and len(node.parts) == 1 and node.parts[0] == "TABLE_DELTA":
|
|
82
75
|
# replace with data
|
|
83
76
|
return Data(table, alias=node.alias)
|
|
84
77
|
|
|
@@ -90,6 +83,7 @@ class TriggerTask(BaseTask):
|
|
|
90
83
|
self.set_error(ret.error_message)
|
|
91
84
|
|
|
92
85
|
except Exception:
|
|
86
|
+
logger.exception("Error during trigger call processing")
|
|
93
87
|
self.set_error(str(traceback.format_exc()))
|
|
94
88
|
|
|
95
89
|
db.session.commit()
|
|
@@ -11,27 +11,28 @@ from mindsdb.api.executor.controllers.session_controller import SessionControlle
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class TriggersController:
|
|
14
|
-
OBJECT_TYPE =
|
|
14
|
+
OBJECT_TYPE = "trigger"
|
|
15
15
|
|
|
16
16
|
def add(self, name, project_name, table, query_str, columns=None):
|
|
17
17
|
name = name.lower()
|
|
18
18
|
|
|
19
19
|
if project_name is None:
|
|
20
|
-
project_name = config.get(
|
|
20
|
+
project_name = config.get("default_project")
|
|
21
21
|
project_controller = ProjectController()
|
|
22
22
|
project = project_controller.get(name=project_name)
|
|
23
23
|
|
|
24
24
|
from mindsdb.api.executor.controllers.session_controller import SessionController
|
|
25
|
+
|
|
25
26
|
session = SessionController()
|
|
26
27
|
|
|
27
28
|
# check exists
|
|
28
29
|
trigger = self.get_trigger_record(name, project_name)
|
|
29
30
|
if trigger is not None:
|
|
30
|
-
raise Exception(f
|
|
31
|
+
raise Exception(f"Trigger already exists: {name}")
|
|
31
32
|
|
|
32
33
|
# check table
|
|
33
34
|
if len(table.parts) < 2:
|
|
34
|
-
raise Exception(f
|
|
35
|
+
raise Exception(f"Database or table not found: {table}")
|
|
35
36
|
|
|
36
37
|
table_name = Identifier(parts=table.parts[1:]).to_string()
|
|
37
38
|
db_name = table.parts[0]
|
|
@@ -39,39 +40,38 @@ class TriggersController:
|
|
|
39
40
|
db_integration = session.integration_controller.get(db_name)
|
|
40
41
|
db_handler = session.integration_controller.get_data_handler(db_name)
|
|
41
42
|
|
|
42
|
-
if not hasattr(db_handler,
|
|
43
|
-
raise Exception(f
|
|
43
|
+
if not hasattr(db_handler, "subscribe"):
|
|
44
|
+
raise Exception(f"Handler {db_integration['engine']} doest support subscription")
|
|
44
45
|
|
|
45
46
|
df = db_handler.get_tables().data_frame
|
|
46
|
-
column =
|
|
47
|
+
column = "table_name"
|
|
47
48
|
if column not in df.columns:
|
|
48
49
|
column = df.columns[0]
|
|
49
50
|
tables = list(df[column])
|
|
50
51
|
|
|
51
52
|
# check only if tables are visible
|
|
52
53
|
if len(tables) > 0 and table_name not in tables:
|
|
53
|
-
raise Exception(f
|
|
54
|
+
raise Exception(f"Table {table_name} not found in {db_name}")
|
|
54
55
|
|
|
55
56
|
columns_str = None
|
|
56
57
|
if columns is not None and len(columns) > 0:
|
|
57
58
|
# join to string with delimiter
|
|
58
|
-
columns_str =
|
|
59
|
+
columns_str = "|".join([col.parts[-1] for col in columns])
|
|
59
60
|
|
|
60
61
|
# check sql
|
|
61
62
|
try:
|
|
62
63
|
parse_sql(query_str)
|
|
63
64
|
except ParsingException as e:
|
|
64
|
-
raise ParsingException(f
|
|
65
|
+
raise ParsingException(f"Unable to parse: {query_str}: {e}") from e
|
|
65
66
|
|
|
66
67
|
# create job record
|
|
67
68
|
record = db.Triggers(
|
|
68
69
|
name=name,
|
|
69
70
|
project_id=project.id,
|
|
70
|
-
|
|
71
|
-
database_id=db_integration['id'],
|
|
71
|
+
database_id=db_integration["id"],
|
|
72
72
|
table_name=table_name,
|
|
73
73
|
query_str=query_str,
|
|
74
|
-
columns=columns_str
|
|
74
|
+
columns=columns_str,
|
|
75
75
|
)
|
|
76
76
|
db.session.add(record)
|
|
77
77
|
db.session.flush()
|
|
@@ -79,7 +79,6 @@ class TriggersController:
|
|
|
79
79
|
task_record = db.Tasks(
|
|
80
80
|
company_id=ctx.company_id,
|
|
81
81
|
user_class=ctx.user_class,
|
|
82
|
-
|
|
83
82
|
object_type=self.OBJECT_TYPE,
|
|
84
83
|
object_id=record.id,
|
|
85
84
|
)
|
|
@@ -110,34 +109,36 @@ class TriggersController:
|
|
|
110
109
|
project_controller = ProjectController()
|
|
111
110
|
project = project_controller.get(name=project_name)
|
|
112
111
|
|
|
113
|
-
query =
|
|
114
|
-
db.Triggers
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
112
|
+
query = (
|
|
113
|
+
db.session.query(db.Triggers)
|
|
114
|
+
.join(db.Tasks, db.Triggers.id == db.Tasks.object_id)
|
|
115
|
+
.filter(
|
|
116
|
+
db.Triggers.project_id == project.id,
|
|
117
|
+
db.Triggers.name == name,
|
|
118
|
+
db.Tasks.object_type == self.OBJECT_TYPE,
|
|
119
|
+
db.Tasks.company_id == ctx.company_id,
|
|
120
|
+
)
|
|
122
121
|
)
|
|
123
122
|
return query.first()
|
|
124
123
|
|
|
125
124
|
def get_list(self, project_name=None):
|
|
126
125
|
session = SessionController()
|
|
127
126
|
|
|
128
|
-
query =
|
|
129
|
-
db.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
127
|
+
query = (
|
|
128
|
+
db.session.query(
|
|
129
|
+
db.Tasks.object_id,
|
|
130
|
+
db.Triggers.project_id,
|
|
131
|
+
db.Triggers.name,
|
|
132
|
+
db.Triggers.database_id,
|
|
133
|
+
db.Triggers.table_name,
|
|
134
|
+
db.Triggers.query_str,
|
|
135
|
+
db.Tasks.last_error,
|
|
136
|
+
)
|
|
137
|
+
.join(db.Triggers, db.Triggers.id == db.Tasks.object_id)
|
|
138
138
|
.filter(
|
|
139
139
|
db.Tasks.object_type == self.OBJECT_TYPE,
|
|
140
140
|
db.Tasks.company_id == ctx.company_id,
|
|
141
|
+
)
|
|
141
142
|
)
|
|
142
143
|
|
|
143
144
|
project_controller = ProjectController()
|
|
@@ -145,24 +146,20 @@ class TriggersController:
|
|
|
145
146
|
project = project_controller.get(name=project_name)
|
|
146
147
|
query = query.filter(db.Triggers.project_id == project.id)
|
|
147
148
|
|
|
148
|
-
database_names = {
|
|
149
|
-
i['id']: i['name']
|
|
150
|
-
for i in session.database_controller.get_list()
|
|
151
|
-
}
|
|
149
|
+
database_names = {i["id"]: i["name"] for i in session.database_controller.get_list()}
|
|
152
150
|
|
|
153
|
-
project_names = {
|
|
154
|
-
i.id: i.name
|
|
155
|
-
for i in project_controller.get_list()
|
|
156
|
-
}
|
|
151
|
+
project_names = {i.id: i.name for i in project_controller.get_list()}
|
|
157
152
|
data = []
|
|
158
153
|
for record in query:
|
|
159
|
-
data.append(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
154
|
+
data.append(
|
|
155
|
+
{
|
|
156
|
+
"id": record.object_id,
|
|
157
|
+
"project": project_names[record.project_id],
|
|
158
|
+
"name": record.name.lower(),
|
|
159
|
+
"database": database_names.get(record.database_id, "?"),
|
|
160
|
+
"table": record.table_name,
|
|
161
|
+
"query": record.query_str,
|
|
162
|
+
"last_error": record.last_error,
|
|
163
|
+
}
|
|
164
|
+
)
|
|
168
165
|
return data
|
mindsdb/migrations/migrate.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from alembic.command import upgrade, autogen
|
|
3
|
+
from alembic.command import upgrade, autogen # noqa
|
|
4
4
|
from alembic.config import Config
|
|
5
5
|
from alembic.script import ScriptDirectory
|
|
6
6
|
from alembic.script.revision import ResolutionError
|
|
@@ -15,20 +15,19 @@ logger = log.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
# This is a migration that is like a 'base version'. Applying only this
|
|
17
17
|
# migration to a fresh DB is equivalent to applying all previous migrations.
|
|
18
|
-
current_checkpoint =
|
|
18
|
+
current_checkpoint = "9f150e4f9a05"
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def apply_checkpoint_migration(script) -> None:
|
|
22
|
-
"""Apply the checkpoint migration to the database.
|
|
23
|
-
"""
|
|
22
|
+
"""Apply the checkpoint migration to the database."""
|
|
24
23
|
with db.engine.begin() as connection:
|
|
25
24
|
context = MigrationContext.configure(
|
|
26
25
|
connection,
|
|
27
26
|
opts={
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
27
|
+
"as_sql": False,
|
|
28
|
+
"starting_rev": None, # ignore current version
|
|
29
|
+
"destination_rev": current_checkpoint,
|
|
30
|
+
},
|
|
32
31
|
)
|
|
33
32
|
revision = script.get_revision(current_checkpoint)
|
|
34
33
|
if not revision:
|
|
@@ -41,7 +40,7 @@ def apply_checkpoint_migration(script) -> None:
|
|
|
41
40
|
|
|
42
41
|
|
|
43
42
|
def get_current_revision() -> str | None:
|
|
44
|
-
"""
|
|
43
|
+
"""Get the current revision of the database.
|
|
45
44
|
|
|
46
45
|
Returns:
|
|
47
46
|
str | None: The current revision of the database.
|
|
@@ -52,17 +51,18 @@ def get_current_revision() -> str | None:
|
|
|
52
51
|
|
|
53
52
|
|
|
54
53
|
def migrate_to_head():
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
"""Trying to update database to head revision.
|
|
55
|
+
If alembic unable to recognize current revision (In case when database version is newer than backend)
|
|
56
|
+
then do nothing.
|
|
58
57
|
"""
|
|
58
|
+
logger.debug("Applying database migrations")
|
|
59
59
|
|
|
60
|
-
config_file = Path(__file__).parent /
|
|
60
|
+
config_file = Path(__file__).parent / "alembic.ini"
|
|
61
61
|
config = Config(config_file)
|
|
62
62
|
|
|
63
63
|
# mindsdb can runs not from project directory
|
|
64
|
-
script_location_abc = config_file.parent / config.get_main_option(
|
|
65
|
-
config.set_main_option(
|
|
64
|
+
script_location_abc = config_file.parent / config.get_main_option("script_location")
|
|
65
|
+
config.set_main_option("script_location", str(script_location_abc))
|
|
66
66
|
|
|
67
67
|
script = ScriptDirectory.from_config(config)
|
|
68
68
|
cur_revision = get_current_revision()
|
|
@@ -81,7 +81,7 @@ def migrate_to_head():
|
|
|
81
81
|
return
|
|
82
82
|
|
|
83
83
|
logger.info("Migrations are available. Applying updates to the database.")
|
|
84
|
-
upgrade(config=config, revision=
|
|
84
|
+
upgrade(config=config, revision="head")
|
|
85
85
|
|
|
86
86
|
|
|
87
87
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
from mindsdb.utilities.config import config
|
|
5
|
+
from mindsdb.utilities import log
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
logger = log.getLogger(__name__)
|
|
9
|
+
_api_status_file = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _get_api_status_file():
|
|
13
|
+
global _api_status_file
|
|
14
|
+
if _api_status_file is None:
|
|
15
|
+
# Use a temporary file that can be shared across processes.
|
|
16
|
+
temp_dir = config["paths"]["tmp"]
|
|
17
|
+
_api_status_file = os.path.join(temp_dir, "mindsdb_api_status.json")
|
|
18
|
+
# Overwrite the file if it exists.
|
|
19
|
+
if os.path.exists(_api_status_file):
|
|
20
|
+
try:
|
|
21
|
+
os.remove(_api_status_file)
|
|
22
|
+
except OSError:
|
|
23
|
+
logger.exception(f"Error removing existing API status file: {_api_status_file}")
|
|
24
|
+
|
|
25
|
+
return _api_status_file
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_api_status():
|
|
29
|
+
"""Get the current API status from the shared file."""
|
|
30
|
+
status_file = _get_api_status_file()
|
|
31
|
+
try:
|
|
32
|
+
if os.path.exists(status_file):
|
|
33
|
+
with open(status_file, "r") as f:
|
|
34
|
+
return json.load(f)
|
|
35
|
+
except (json.JSONDecodeError, IOError):
|
|
36
|
+
pass
|
|
37
|
+
return {}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def set_api_status(api_name: str, status: bool):
|
|
41
|
+
"""Set the status of an API in the shared file."""
|
|
42
|
+
status_file = _get_api_status_file()
|
|
43
|
+
current_status = get_api_status()
|
|
44
|
+
current_status[api_name] = status
|
|
45
|
+
|
|
46
|
+
# Write atomically to avoid race conditions.
|
|
47
|
+
temp_file = status_file + ".tmp"
|
|
48
|
+
try:
|
|
49
|
+
with open(temp_file, "w") as f:
|
|
50
|
+
json.dump(current_status, f)
|
|
51
|
+
os.replace(temp_file, status_file)
|
|
52
|
+
except IOError:
|
|
53
|
+
# Clean up temp file if it exists.
|
|
54
|
+
if os.path.exists(temp_file):
|
|
55
|
+
try:
|
|
56
|
+
os.remove(temp_file)
|
|
57
|
+
except OSError:
|
|
58
|
+
pass
|
mindsdb/utilities/config.py
CHANGED
|
@@ -170,6 +170,7 @@ class Config:
|
|
|
170
170
|
"restart_on_failure": True,
|
|
171
171
|
"max_restart_count": 1,
|
|
172
172
|
"max_restart_interval_seconds": 60,
|
|
173
|
+
"a2wsgi": {"workers": 10, "send_queue_size": 10},
|
|
173
174
|
},
|
|
174
175
|
"mysql": {
|
|
175
176
|
"host": api_host,
|
|
@@ -300,6 +301,54 @@ class Config:
|
|
|
300
301
|
self._env_config["default_reranking_model"] = {
|
|
301
302
|
"api_key": os.environ["MINDSDB_DEFAULT_RERANKING_MODEL_API_KEY"]
|
|
302
303
|
}
|
|
304
|
+
|
|
305
|
+
# Reranker configuration from environment variables
|
|
306
|
+
reranker_config = {}
|
|
307
|
+
if os.environ.get("MINDSDB_RERANKER_N", "") != "":
|
|
308
|
+
try:
|
|
309
|
+
reranker_config["n"] = int(os.environ["MINDSDB_RERANKER_N"])
|
|
310
|
+
except ValueError:
|
|
311
|
+
raise ValueError(f"MINDSDB_RERANKER_N must be an integer, got: {os.environ['MINDSDB_RERANKER_N']}")
|
|
312
|
+
|
|
313
|
+
if os.environ.get("MINDSDB_RERANKER_LOGPROBS", "") != "":
|
|
314
|
+
logprobs_value = os.environ["MINDSDB_RERANKER_LOGPROBS"].lower()
|
|
315
|
+
if logprobs_value in ("true", "1", "yes", "y"):
|
|
316
|
+
reranker_config["logprobs"] = True
|
|
317
|
+
elif logprobs_value in ("false", "0", "no", "n"):
|
|
318
|
+
reranker_config["logprobs"] = False
|
|
319
|
+
else:
|
|
320
|
+
raise ValueError(
|
|
321
|
+
f"MINDSDB_RERANKER_LOGPROBS must be a boolean value, got: {os.environ['MINDSDB_RERANKER_LOGPROBS']}"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if os.environ.get("MINDSDB_RERANKER_TOP_LOGPROBS", "") != "":
|
|
325
|
+
try:
|
|
326
|
+
reranker_config["top_logprobs"] = int(os.environ["MINDSDB_RERANKER_TOP_LOGPROBS"])
|
|
327
|
+
except ValueError:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
f"MINDSDB_RERANKER_TOP_LOGPROBS must be an integer, got: {os.environ['MINDSDB_RERANKER_TOP_LOGPROBS']}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
if os.environ.get("MINDSDB_RERANKER_MAX_TOKENS", "") != "":
|
|
333
|
+
try:
|
|
334
|
+
reranker_config["max_tokens"] = int(os.environ["MINDSDB_RERANKER_MAX_TOKENS"])
|
|
335
|
+
except ValueError:
|
|
336
|
+
raise ValueError(
|
|
337
|
+
f"MINDSDB_RERANKER_MAX_TOKENS must be an integer, got: {os.environ['MINDSDB_RERANKER_MAX_TOKENS']}"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
if os.environ.get("MINDSDB_RERANKER_VALID_CLASS_TOKENS", "") != "":
|
|
341
|
+
try:
|
|
342
|
+
reranker_config["valid_class_tokens"] = os.environ["MINDSDB_RERANKER_VALID_CLASS_TOKENS"].split(",")
|
|
343
|
+
except ValueError:
|
|
344
|
+
raise ValueError(
|
|
345
|
+
f"MINDSDB_RERANKER_VALID_CLASS_TOKENS must be a comma-separated list of strings, got: {os.environ['MINDSDB_RERANKER_VALID_CLASS_TOKENS']}"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
if reranker_config:
|
|
349
|
+
if "default_reranking_model" not in self._env_config:
|
|
350
|
+
self._env_config["default_reranking_model"] = {}
|
|
351
|
+
self._env_config["default_reranking_model"].update(reranker_config)
|
|
303
352
|
if os.environ.get("MINDSDB_DATA_CATALOG_ENABLED", "").lower() in ("1", "true"):
|
|
304
353
|
self._env_config["data_catalog"] = {"enabled": True}
|
|
305
354
|
|
mindsdb/utilities/exception.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
from textwrap import indent
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import ERR
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MindsDBError(Exception):
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseEntityException(MindsDBError):
|
|
5
12
|
"""Base exception for entitys errors
|
|
6
13
|
|
|
7
14
|
Attributes:
|
|
@@ -35,6 +42,38 @@ class EntityNotExistsError(BaseEntityException):
|
|
|
35
42
|
super().__init__(message, entity_name)
|
|
36
43
|
|
|
37
44
|
|
|
45
|
+
class ParsingError(MindsDBError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class QueryError(MindsDBError):
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
db_name: str | None = None,
|
|
53
|
+
db_type: str | None = None,
|
|
54
|
+
db_error_msg: str | None = None,
|
|
55
|
+
failed_query: str | None = None,
|
|
56
|
+
is_external: bool = True,
|
|
57
|
+
is_acceptable: bool = False,
|
|
58
|
+
) -> None:
|
|
59
|
+
self.mysql_error_code = ERR.ER_UNKNOWN_ERROR
|
|
60
|
+
self.db_name = db_name
|
|
61
|
+
self.db_type = db_type
|
|
62
|
+
self.db_error_msg = db_error_msg
|
|
63
|
+
self.failed_query = failed_query
|
|
64
|
+
self.is_external = is_external
|
|
65
|
+
self.is_acceptable = is_acceptable
|
|
66
|
+
|
|
67
|
+
def __str__(self) -> str:
|
|
68
|
+
return format_db_error_message(
|
|
69
|
+
db_name=self.db_name,
|
|
70
|
+
db_type=self.db_type,
|
|
71
|
+
db_error_msg=self.db_error_msg,
|
|
72
|
+
failed_query=self.failed_query,
|
|
73
|
+
is_external=self.is_external,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
38
77
|
def format_db_error_message(
|
|
39
78
|
db_name: str | None = None,
|
|
40
79
|
db_type: str | None = None,
|
mindsdb/utilities/fs.py
CHANGED
|
@@ -46,28 +46,31 @@ def send_profiling_results(profiling_data: dict):
|
|
|
46
46
|
user=MINDSDB_PROFILING_DB_USER,
|
|
47
47
|
password=MINDSDB_PROFILING_DB_PASSWORD,
|
|
48
48
|
dbname="postgres",
|
|
49
|
-
connect_timeout=5
|
|
49
|
+
connect_timeout=5,
|
|
50
50
|
)
|
|
51
51
|
except Exception:
|
|
52
|
-
logger.
|
|
52
|
+
logger.warning("cant get acceess to profiling database")
|
|
53
53
|
return
|
|
54
54
|
cur = connection.cursor()
|
|
55
|
-
cur.execute(
|
|
55
|
+
cur.execute(
|
|
56
|
+
"""
|
|
56
57
|
insert into profiling
|
|
57
58
|
(data, query, time, hostname, environment, api, total_time, company_id, instance_id)
|
|
58
59
|
values
|
|
59
60
|
(%s, %s, %s, %s, %s, %s, %s, %s, %s)
|
|
60
|
-
""",
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
""",
|
|
62
|
+
(
|
|
63
|
+
json.dumps(profiling["tree"]),
|
|
64
|
+
profiling.get("query", "?"),
|
|
65
|
+
time_start_at,
|
|
66
|
+
profiling["hostname"],
|
|
67
|
+
profiling.get("environment", "?"),
|
|
68
|
+
profiling.get("api", "?"),
|
|
69
|
+
profiling["tree"]["value"],
|
|
70
|
+
profiling["company_id"],
|
|
71
|
+
profiling["instance_id"],
|
|
72
|
+
),
|
|
73
|
+
)
|
|
71
74
|
|
|
72
75
|
connection.commit()
|
|
73
76
|
cur.close()
|