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.

Files changed (120) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +39 -20
  3. mindsdb/api/a2a/agent.py +7 -9
  4. mindsdb/api/a2a/common/server/server.py +3 -3
  5. mindsdb/api/a2a/common/server/task_manager.py +4 -4
  6. mindsdb/api/a2a/task_manager.py +15 -17
  7. mindsdb/api/common/middleware.py +9 -11
  8. mindsdb/api/executor/command_executor.py +2 -4
  9. mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
  10. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +100 -48
  11. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
  12. mindsdb/api/executor/datahub/datanodes/system_tables.py +1 -1
  13. mindsdb/api/executor/exceptions.py +29 -10
  14. mindsdb/api/executor/planner/plan_join.py +17 -3
  15. mindsdb/api/executor/sql_query/sql_query.py +74 -74
  16. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
  17. mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
  18. mindsdb/api/executor/utilities/functions.py +6 -6
  19. mindsdb/api/executor/utilities/sql.py +32 -16
  20. mindsdb/api/http/gui.py +5 -11
  21. mindsdb/api/http/initialize.py +8 -10
  22. mindsdb/api/http/namespaces/agents.py +10 -12
  23. mindsdb/api/http/namespaces/analysis.py +13 -20
  24. mindsdb/api/http/namespaces/auth.py +1 -1
  25. mindsdb/api/http/namespaces/config.py +15 -11
  26. mindsdb/api/http/namespaces/databases.py +140 -201
  27. mindsdb/api/http/namespaces/file.py +15 -4
  28. mindsdb/api/http/namespaces/handlers.py +7 -2
  29. mindsdb/api/http/namespaces/knowledge_bases.py +8 -7
  30. mindsdb/api/http/namespaces/models.py +94 -126
  31. mindsdb/api/http/namespaces/projects.py +13 -22
  32. mindsdb/api/http/namespaces/sql.py +33 -25
  33. mindsdb/api/http/namespaces/tab.py +27 -37
  34. mindsdb/api/http/namespaces/views.py +1 -1
  35. mindsdb/api/http/start.py +14 -8
  36. mindsdb/api/mcp/__init__.py +2 -1
  37. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
  38. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
  39. mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
  40. mindsdb/api/postgres/postgres_proxy/executor/executor.py +6 -13
  41. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +40 -28
  42. mindsdb/integrations/handlers/byom_handler/byom_handler.py +168 -185
  43. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +11 -5
  44. mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
  45. mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
  46. mindsdb/integrations/handlers/openai_handler/openai_handler.py +1 -1
  47. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +20 -2
  48. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +18 -3
  49. mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
  50. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
  51. mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
  52. mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
  53. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
  54. mindsdb/integrations/libs/api_handler.py +10 -10
  55. mindsdb/integrations/libs/base.py +4 -4
  56. mindsdb/integrations/libs/llm/utils.py +2 -2
  57. mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
  58. mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
  59. mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
  60. mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
  61. mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
  62. mindsdb/integrations/libs/process_cache.py +132 -140
  63. mindsdb/integrations/libs/response.py +18 -12
  64. mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
  65. mindsdb/integrations/utilities/files/file_reader.py +6 -7
  66. mindsdb/integrations/utilities/rag/config_loader.py +37 -26
  67. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +59 -9
  68. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
  69. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
  70. mindsdb/integrations/utilities/rag/settings.py +58 -133
  71. mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
  72. mindsdb/interfaces/agents/agents_controller.py +2 -1
  73. mindsdb/interfaces/agents/constants.py +0 -2
  74. mindsdb/interfaces/agents/litellm_server.py +34 -58
  75. mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
  76. mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
  77. mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
  78. mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
  79. mindsdb/interfaces/chatbot/polling.py +30 -18
  80. mindsdb/interfaces/data_catalog/data_catalog_loader.py +10 -10
  81. mindsdb/interfaces/database/integrations.py +19 -2
  82. mindsdb/interfaces/file/file_controller.py +6 -6
  83. mindsdb/interfaces/functions/controller.py +1 -1
  84. mindsdb/interfaces/functions/to_markdown.py +2 -2
  85. mindsdb/interfaces/jobs/jobs_controller.py +5 -5
  86. mindsdb/interfaces/jobs/scheduler.py +3 -8
  87. mindsdb/interfaces/knowledge_base/controller.py +54 -25
  88. mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
  89. mindsdb/interfaces/model/model_controller.py +170 -166
  90. mindsdb/interfaces/query_context/context_controller.py +14 -2
  91. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +6 -4
  92. mindsdb/interfaces/skills/retrieval_tool.py +43 -50
  93. mindsdb/interfaces/skills/skill_tool.py +2 -2
  94. mindsdb/interfaces/skills/sql_agent.py +25 -19
  95. mindsdb/interfaces/storage/fs.py +114 -169
  96. mindsdb/interfaces/storage/json.py +19 -18
  97. mindsdb/interfaces/storage/model_fs.py +54 -92
  98. mindsdb/interfaces/tabs/tabs_controller.py +49 -72
  99. mindsdb/interfaces/tasks/task_monitor.py +3 -9
  100. mindsdb/interfaces/tasks/task_thread.py +7 -9
  101. mindsdb/interfaces/triggers/trigger_task.py +7 -13
  102. mindsdb/interfaces/triggers/triggers_controller.py +47 -50
  103. mindsdb/migrations/migrate.py +16 -16
  104. mindsdb/utilities/api_status.py +58 -0
  105. mindsdb/utilities/config.py +49 -0
  106. mindsdb/utilities/exception.py +40 -1
  107. mindsdb/utilities/fs.py +0 -1
  108. mindsdb/utilities/hooks/profiling.py +17 -14
  109. mindsdb/utilities/langfuse.py +40 -45
  110. mindsdb/utilities/log.py +272 -0
  111. mindsdb/utilities/ml_task_queue/consumer.py +52 -58
  112. mindsdb/utilities/ml_task_queue/producer.py +26 -30
  113. mindsdb/utilities/render/sqlalchemy_render.py +8 -7
  114. mindsdb/utilities/utils.py +2 -2
  115. {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/METADATA +266 -261
  116. {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/RECORD +119 -119
  117. mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
  118. {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/WHEEL +0 -0
  119. {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/licenses/LICENSE +0 -0
  120. {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 as e:
43
- logger.error(e)
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'Task starting: {self.object_type}.{self.object_id}')
36
+ logger.info(f"Task starting: {self.object_type}.{self.object_id}")
38
37
  try:
39
- if self.object_type == 'trigger':
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 == 'chatbot':
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 == 'query':
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.error(traceback.format_exc())
54
- task_record.last_error = str(traceback.format_exc())
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'Task stopping: {self.object_type}.{self.object_id}')
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['name'])
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'trigger call: {row}, {key}')
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 = 'trigger'
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('default_project')
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'Trigger already exists: {name}')
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'Database or table not found: {table}')
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, 'subscribe'):
43
- raise Exception(f'Handler {db_integration["engine"]} does''t support subscription')
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 = 'table_name'
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'Table {table_name} not found in {db_name}')
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 = '|'.join([col.parts[-1] for col in columns])
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'Unable to parse: {query_str}: {e}')
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 = db.session.query(
114
- db.Triggers
115
- ).join(
116
- db.Tasks, db.Triggers.id == db.Tasks.object_id
117
- ).filter(
118
- db.Triggers.project_id == project.id,
119
- db.Triggers.name == name,
120
- db.Tasks.object_type == self.OBJECT_TYPE,
121
- db.Tasks.company_id == ctx.company_id,
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 = 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)\
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
- 'id': record.object_id,
161
- 'project': project_names[record.project_id],
162
- 'name': record.name.lower(),
163
- 'database': database_names.get(record.database_id, '?'),
164
- 'table': record.table_name,
165
- 'query': record.query_str,
166
- 'last_error': record.last_error,
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
@@ -1,6 +1,6 @@
1
1
  from pathlib import Path
2
2
 
3
- from alembic.command import upgrade, autogen # noqa
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 = '9f150e4f9a05'
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
- 'as_sql': False,
29
- 'starting_rev': None, # ignore current version
30
- 'destination_rev': current_checkpoint,
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
- """ Get the current revision of the database.
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
- """ Trying to update database to head revision.
56
- If alembic unable to recognize current revision (In case when database version is newer than backend)
57
- then do nothing.
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 / 'alembic.ini'
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('script_location')
65
- config.set_main_option('script_location', str(script_location_abc))
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='head')
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
@@ -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
 
@@ -1,7 +1,14 @@
1
1
  from textwrap import indent
2
2
 
3
3
 
4
- class BaseEntityException(Exception):
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
@@ -118,7 +118,6 @@ def clean_unlinked_process_marks() -> List[int]:
118
118
 
119
119
  except psutil.AccessDenied:
120
120
  logger.warning(f"access to {process_id} denied")
121
-
122
121
  continue
123
122
 
124
123
  except psutil.NoSuchProcess:
@@ -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.error('cant get acceess to profiling database')
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
- json.dumps(profiling["tree"]),
62
- profiling.get("query", "?"),
63
- time_start_at,
64
- profiling["hostname"],
65
- profiling.get("environment", "?"),
66
- profiling.get("api", "?"),
67
- profiling["tree"]["value"],
68
- profiling["company_id"],
69
- profiling["instance_id"]
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()