MindsDB 25.9.2.0a1__py3-none-any.whl → 25.10.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 (164) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +40 -29
  3. mindsdb/api/a2a/__init__.py +1 -1
  4. mindsdb/api/a2a/agent.py +16 -10
  5. mindsdb/api/a2a/common/server/server.py +7 -3
  6. mindsdb/api/a2a/common/server/task_manager.py +12 -5
  7. mindsdb/api/a2a/common/types.py +66 -0
  8. mindsdb/api/a2a/task_manager.py +65 -17
  9. mindsdb/api/common/middleware.py +10 -12
  10. mindsdb/api/executor/command_executor.py +51 -40
  11. mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
  12. mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
  13. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
  14. mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
  15. mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
  16. mindsdb/api/executor/exceptions.py +29 -10
  17. mindsdb/api/executor/planner/plan_join.py +17 -3
  18. mindsdb/api/executor/planner/query_prepare.py +2 -20
  19. mindsdb/api/executor/sql_query/sql_query.py +74 -74
  20. mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
  21. mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
  22. mindsdb/api/executor/utilities/functions.py +6 -6
  23. mindsdb/api/executor/utilities/sql.py +37 -20
  24. mindsdb/api/http/gui.py +5 -11
  25. mindsdb/api/http/initialize.py +75 -61
  26. mindsdb/api/http/namespaces/agents.py +10 -15
  27. mindsdb/api/http/namespaces/analysis.py +13 -20
  28. mindsdb/api/http/namespaces/auth.py +1 -1
  29. mindsdb/api/http/namespaces/chatbots.py +0 -5
  30. mindsdb/api/http/namespaces/config.py +15 -11
  31. mindsdb/api/http/namespaces/databases.py +140 -201
  32. mindsdb/api/http/namespaces/file.py +17 -4
  33. mindsdb/api/http/namespaces/handlers.py +17 -7
  34. mindsdb/api/http/namespaces/knowledge_bases.py +28 -7
  35. mindsdb/api/http/namespaces/models.py +94 -126
  36. mindsdb/api/http/namespaces/projects.py +13 -22
  37. mindsdb/api/http/namespaces/sql.py +33 -25
  38. mindsdb/api/http/namespaces/tab.py +27 -37
  39. mindsdb/api/http/namespaces/views.py +1 -1
  40. mindsdb/api/http/start.py +16 -10
  41. mindsdb/api/mcp/__init__.py +2 -1
  42. mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
  43. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
  44. mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
  45. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +8 -2
  46. mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
  47. mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
  48. mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
  49. mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
  50. mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
  51. mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
  52. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
  53. mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
  54. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
  55. mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
  56. mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
  57. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
  58. mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
  59. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +14 -2
  60. mindsdb/integrations/handlers/shopify_handler/requirements.txt +1 -0
  61. mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +80 -13
  62. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
  63. mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
  64. mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
  65. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
  66. mindsdb/integrations/handlers/zendesk_handler/zendesk_tables.py +144 -111
  67. mindsdb/integrations/libs/api_handler.py +10 -10
  68. mindsdb/integrations/libs/base.py +4 -4
  69. mindsdb/integrations/libs/llm/utils.py +2 -2
  70. mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
  71. mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
  72. mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
  73. mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
  74. mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
  75. mindsdb/integrations/libs/process_cache.py +132 -140
  76. mindsdb/integrations/libs/response.py +18 -12
  77. mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
  78. mindsdb/integrations/utilities/files/file_reader.py +6 -7
  79. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/__init__.py +1 -0
  80. mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
  81. mindsdb/integrations/utilities/rag/config_loader.py +37 -26
  82. mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
  83. mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
  84. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
  85. mindsdb/integrations/utilities/rag/settings.py +58 -133
  86. mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
  87. mindsdb/interfaces/agents/agents_controller.py +2 -3
  88. mindsdb/interfaces/agents/constants.py +0 -2
  89. mindsdb/interfaces/agents/litellm_server.py +34 -58
  90. mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
  91. mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
  92. mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
  93. mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
  94. mindsdb/interfaces/chatbot/polling.py +30 -18
  95. mindsdb/interfaces/data_catalog/data_catalog_loader.py +16 -17
  96. mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
  97. mindsdb/interfaces/database/data_handlers_cache.py +190 -0
  98. mindsdb/interfaces/database/database.py +3 -3
  99. mindsdb/interfaces/database/integrations.py +7 -110
  100. mindsdb/interfaces/database/projects.py +2 -6
  101. mindsdb/interfaces/database/views.py +1 -4
  102. mindsdb/interfaces/file/file_controller.py +6 -6
  103. mindsdb/interfaces/functions/controller.py +1 -1
  104. mindsdb/interfaces/functions/to_markdown.py +2 -2
  105. mindsdb/interfaces/jobs/jobs_controller.py +5 -9
  106. mindsdb/interfaces/jobs/scheduler.py +3 -9
  107. mindsdb/interfaces/knowledge_base/controller.py +244 -128
  108. mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
  109. mindsdb/interfaces/knowledge_base/executor.py +11 -0
  110. mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
  111. mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
  112. mindsdb/interfaces/model/model_controller.py +172 -168
  113. mindsdb/interfaces/query_context/context_controller.py +14 -2
  114. mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
  115. mindsdb/interfaces/skills/retrieval_tool.py +43 -50
  116. mindsdb/interfaces/skills/skill_tool.py +2 -2
  117. mindsdb/interfaces/skills/skills_controller.py +1 -4
  118. mindsdb/interfaces/skills/sql_agent.py +25 -19
  119. mindsdb/interfaces/storage/db.py +16 -6
  120. mindsdb/interfaces/storage/fs.py +114 -169
  121. mindsdb/interfaces/storage/json.py +19 -18
  122. mindsdb/interfaces/tabs/tabs_controller.py +49 -72
  123. mindsdb/interfaces/tasks/task_monitor.py +3 -9
  124. mindsdb/interfaces/tasks/task_thread.py +7 -9
  125. mindsdb/interfaces/triggers/trigger_task.py +7 -13
  126. mindsdb/interfaces/triggers/triggers_controller.py +47 -52
  127. mindsdb/migrations/migrate.py +16 -16
  128. mindsdb/utilities/api_status.py +58 -0
  129. mindsdb/utilities/config.py +68 -2
  130. mindsdb/utilities/exception.py +40 -1
  131. mindsdb/utilities/fs.py +0 -1
  132. mindsdb/utilities/hooks/profiling.py +17 -14
  133. mindsdb/utilities/json_encoder.py +24 -10
  134. mindsdb/utilities/langfuse.py +40 -45
  135. mindsdb/utilities/log.py +272 -0
  136. mindsdb/utilities/ml_task_queue/consumer.py +52 -58
  137. mindsdb/utilities/ml_task_queue/producer.py +26 -30
  138. mindsdb/utilities/render/sqlalchemy_render.py +22 -20
  139. mindsdb/utilities/starters.py +0 -10
  140. mindsdb/utilities/utils.py +2 -2
  141. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/METADATA +286 -267
  142. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/RECORD +145 -159
  143. mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
  144. mindsdb/api/postgres/__init__.py +0 -0
  145. mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
  146. mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
  147. mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -189
  148. mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
  149. mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
  150. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
  151. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
  152. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
  153. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
  154. mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -253
  155. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
  156. mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
  157. mindsdb/api/postgres/start.py +0 -11
  158. mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
  159. mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
  160. mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
  161. mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
  162. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/WHEEL +0 -0
  163. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/licenses/LICENSE +0 -0
  164. {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,7 @@ class JsonStorage:
23
23
  resource_group=self.resource_group,
24
24
  resource_id=self.resource_id,
25
25
  company_id=ctx.company_id,
26
- content=value
26
+ content=value,
27
27
  )
28
28
  db.session.add(record)
29
29
  else:
@@ -43,26 +43,27 @@ class JsonStorage:
43
43
  return self[key]
44
44
 
45
45
  def get_record(self, key):
46
- record = db.session.query(db.JsonStorage).filter_by(
47
- name=key,
48
- resource_group=self.resource_group,
49
- resource_id=self.resource_id,
50
- company_id=ctx.company_id
51
- ).first()
46
+ record = (
47
+ db.session.query(db.JsonStorage)
48
+ .filter_by(
49
+ name=key, resource_group=self.resource_group, resource_id=self.resource_id, company_id=ctx.company_id
50
+ )
51
+ .first()
52
+ )
52
53
  return record
53
54
 
54
55
  def get_all_records(self):
55
- records = db.session.query(db.JsonStorage).filter_by(
56
- resource_group=self.resource_group,
57
- resource_id=self.resource_id,
58
- company_id=ctx.company_id
59
- ).all()
56
+ records = (
57
+ db.session.query(db.JsonStorage)
58
+ .filter_by(resource_group=self.resource_group, resource_id=self.resource_id, company_id=ctx.company_id)
59
+ .all()
60
+ )
60
61
  return records
61
62
 
62
63
  def __repr__(self):
63
64
  records = self.get_all_records()
64
65
  names = [x.name for x in records]
65
- return f'json_storage({names})'
66
+ return f"json_storage({names})"
66
67
 
67
68
  def __len__(self):
68
69
  records = self.get_all_records()
@@ -76,7 +77,7 @@ class JsonStorage:
76
77
  db.session.commit()
77
78
  except Exception:
78
79
  db.session.rollback()
79
- logger.error('cant delete record from JSON storage')
80
+ logger.exception("cant delete record from JSON storage:")
80
81
 
81
82
  def delete(self, key):
82
83
  del self[key]
@@ -89,13 +90,13 @@ class JsonStorage:
89
90
  db.session.commit()
90
91
  except Exception:
91
92
  db.session.rollback()
92
- logger.error('cant delete records from JSON storage')
93
+ logger.exception("cant delete records from JSON storage:")
93
94
 
94
95
 
95
96
  class EncryptedJsonStorage(JsonStorage):
96
97
  def __init__(self, resource_group: str, resource_id: int):
97
98
  super().__init__(resource_group, resource_id)
98
- self.secret_key = config.get('secret_key', 'dummy-key')
99
+ self.secret_key = config.get("secret_key", "dummy-key")
99
100
 
100
101
  def __setitem__(self, key: str, value: dict) -> None:
101
102
  if isinstance(value, dict) is False:
@@ -110,7 +111,7 @@ class EncryptedJsonStorage(JsonStorage):
110
111
  resource_group=self.resource_group,
111
112
  resource_id=self.resource_id,
112
113
  company_id=ctx.company_id,
113
- encrypted_content=encrypted_value
114
+ encrypted_content=encrypted_value,
114
115
  )
115
116
  db.session.add(record)
116
117
  else:
@@ -125,7 +126,7 @@ class EncryptedJsonStorage(JsonStorage):
125
126
  resource_group=self.resource_group,
126
127
  resource_id=self.resource_id,
127
128
  company_id=ctx.company_id,
128
- encrypted_content=encrypted_value
129
+ encrypted_content=encrypted_value,
129
130
  )
130
131
  db.session.add(record)
131
132
  else:
@@ -11,16 +11,13 @@ from mindsdb.interfaces.storage.fs import FileStorageFactory, RESOURCE_GROUP, Fi
11
11
  logger = log.getLogger(__name__)
12
12
 
13
13
 
14
- TABS_FILENAME = 'tabs'
14
+ TABS_FILENAME = "tabs"
15
15
 
16
16
 
17
17
  def get_storage():
18
18
  # deprecated
19
19
 
20
- storageFactory = FileStorageFactory(
21
- resource_group=RESOURCE_GROUP.TAB,
22
- sync=True
23
- )
20
+ storageFactory = FileStorageFactory(resource_group=RESOURCE_GROUP.TAB, sync=True)
24
21
 
25
22
  # resource_id is useless for 'tabs'
26
23
  # use constant
@@ -35,10 +32,7 @@ class TabsController:
35
32
  """
36
33
 
37
34
  def __init__(self) -> None:
38
- self.storage_factory = FileStorageFactory(
39
- resource_group=RESOURCE_GROUP.TAB,
40
- sync=True
41
- )
35
+ self.storage_factory = FileStorageFactory(resource_group=RESOURCE_GROUP.TAB, sync=True)
42
36
 
43
37
  def _get_file_storage(self) -> FileStorage:
44
38
  """Get user's tabs file storage
@@ -69,9 +63,9 @@ class TabsController:
69
63
  """
70
64
  tabs = {}
71
65
  for child in self._get_file_storage().folder_path.iterdir():
72
- if (child.is_file() and child.name.startswith('tab_')) is False:
66
+ if (child.is_file() and child.name.startswith("tab_")) is False:
73
67
  continue
74
- tab_id = child.name.replace('tab_', '')
68
+ tab_id = child.name.replace("tab_", "")
75
69
  if tab_id.isnumeric() is False:
76
70
  continue
77
71
  tabs[int(tab_id)] = child
@@ -85,12 +79,11 @@ class TabsController:
85
79
  """
86
80
  all_tabs = self.get_all()
87
81
  for tab in all_tabs:
88
- del tab['content']
82
+ del tab["content"]
89
83
  return all_tabs
90
84
 
91
85
  def _migrate_legacy(self) -> None:
92
- """Convert old single-file tabs storage to multiple files
93
- """
86
+ """Convert old single-file tabs storage to multiple files"""
94
87
  file_storage = self._get_file_storage()
95
88
  try:
96
89
  file_data = file_storage.file_get(TABS_FILENAME)
@@ -106,12 +99,12 @@ class TabsController:
106
99
  file_storage.delete()
107
100
  return
108
101
 
109
- if isinstance(data, dict) is False or isinstance(data.get('tabs'), str) is False:
102
+ if isinstance(data, dict) is False or isinstance(data.get("tabs"), str) is False:
110
103
  file_storage.delete()
111
104
  return
112
105
 
113
106
  try:
114
- tabs_list = json.loads(data['tabs'])
107
+ tabs_list = json.loads(data["tabs"])
115
108
  except Exception:
116
109
  file_storage.delete()
117
110
  return
@@ -123,12 +116,10 @@ class TabsController:
123
116
  for tab in tabs_list:
124
117
  tab_id = self._get_next_tab_id()
125
118
 
126
- b_types = json.dumps({
127
- 'index': tab.get('index', 0),
128
- 'name': tab.get('name', 'undefined'),
129
- 'content': tab.get('value', '')
130
- }).encode("utf-8")
131
- file_storage.file_set(f'tab_{tab_id}', b_types)
119
+ b_types = json.dumps(
120
+ {"index": tab.get("index", 0), "name": tab.get("name", "undefined"), "content": tab.get("value", "")}
121
+ ).encode("utf-8")
122
+ file_storage.file_set(f"tab_{tab_id}", b_types)
132
123
 
133
124
  file_storage.delete(TABS_FILENAME)
134
125
 
@@ -149,12 +140,9 @@ class TabsController:
149
140
  except Exception as e:
150
141
  logger.error(f"Can't read data of tab {ctx.company_id}/{tab_id}: {e}")
151
142
  continue
152
- tabs_list.append({
153
- 'id': tab_id,
154
- **data
155
- })
143
+ tabs_list.append({"id": tab_id, **data})
156
144
 
157
- tabs_list.sort(key=lambda x: x['index'])
145
+ tabs_list.sort(key=lambda x: x["index"])
158
146
  return tabs_list
159
147
 
160
148
  def get(self, tab_id: int) -> Dict:
@@ -167,25 +155,22 @@ class TabsController:
167
155
  dict: tabs data
168
156
  """
169
157
  if isinstance(tab_id, int) is False:
170
- raise ValueError('Tab id must be integer')
158
+ raise ValueError("Tab id must be integer")
171
159
 
172
160
  try:
173
- raw_tab_data = self._get_file_storage().file_get(f'tab_{tab_id}')
174
- except FileNotFoundError:
175
- raise EntityNotExistsError(f'tab {tab_id}')
161
+ raw_tab_data = self._get_file_storage().file_get(f"tab_{tab_id}")
162
+ except FileNotFoundError as e:
163
+ raise EntityNotExistsError(f"tab {tab_id}") from e
176
164
 
177
165
  try:
178
166
  data = json.loads(raw_tab_data)
179
167
  except Exception as e:
180
168
  logger.error(f"Can't read data of tab {ctx.company_id}/{tab_id}: {e}")
181
- raise Exception(f"Can't read data of tab: {e}")
169
+ raise Exception(f"Can't read data of tab: {e}") from e
182
170
 
183
- return {
184
- 'id': tab_id,
185
- **data
186
- }
171
+ return {"id": tab_id, **data}
187
172
 
188
- def add(self, index: int = None, name: str = 'undefined', content: str = '') -> Dict:
173
+ def add(self, index: int = None, name: str = "undefined", content: str = "") -> Dict:
189
174
  """Add new tab
190
175
 
191
176
  Args:
@@ -205,27 +190,23 @@ class TabsController:
205
190
  if len(all_tabs) == 0:
206
191
  index = 0
207
192
  else:
208
- index = max([x.get('index', 0) for x in all_tabs]) + 1
193
+ index = max([x.get("index", 0) for x in all_tabs]) + 1
209
194
 
210
- data_bytes = json.dumps({
211
- 'index': index,
212
- 'name': name,
213
- 'content': content
214
- }).encode("utf-8")
215
- file_storage.file_set(f'tab_{tab_id}', data_bytes)
195
+ data_bytes = json.dumps({"index": index, "name": name, "content": content}).encode("utf-8")
196
+ file_storage.file_set(f"tab_{tab_id}", data_bytes)
216
197
 
217
198
  if reorder_required:
218
199
  all_tabs = self.get_all()
219
- all_tabs.sort(key=lambda x: (x['index'], 0 if x['id'] == tab_id else 1))
200
+ all_tabs.sort(key=lambda x: (x["index"], 0 if x["id"] == tab_id else 1))
220
201
  file_storage.sync = False
221
202
  for tab_index, tab in enumerate(all_tabs):
222
- tab['index'] = tab_index
223
- data_bytes = json.dumps(tab).encode('utf-8')
224
- file_storage.file_set(f'tab_{tab["id"]}', data_bytes)
203
+ tab["index"] = tab_index
204
+ data_bytes = json.dumps(tab).encode("utf-8")
205
+ file_storage.file_set(f"tab_{tab['id']}", data_bytes)
225
206
  file_storage.sync = True
226
207
  file_storage.push()
227
208
 
228
- return {'id': tab_id, 'index': index, 'name': name}
209
+ return {"id": tab_id, "index": index, "name": name}
229
210
 
230
211
  def modify(self, tab_id: int, index: int = None, name: str = None, content: str = None) -> Dict:
231
212
  """Modify the tab
@@ -243,49 +224,45 @@ class TabsController:
243
224
  current_data = self.get(tab_id)
244
225
 
245
226
  # region modify index
246
- if index is not None and current_data['index'] != index:
247
- current_data['index'] = index
248
- all_tabs = [x for x in self.get_all() if x['id'] != tab_id]
249
- all_tabs.sort(key=lambda x: x['index'])
227
+ if index is not None and current_data["index"] != index:
228
+ current_data["index"] = index
229
+ all_tabs = [x for x in self.get_all() if x["id"] != tab_id]
230
+ all_tabs.sort(key=lambda x: x["index"])
250
231
  file_storage.sync = False
251
232
  for tab_index, tab in enumerate(all_tabs):
252
233
  if tab_index < index:
253
- tab['index'] = tab_index
234
+ tab["index"] = tab_index
254
235
  else:
255
- tab['index'] = tab_index + 1
256
- data_bytes = json.dumps(tab).encode('utf-8')
257
- file_storage.file_set(f'tab_{tab["id"]}', data_bytes)
236
+ tab["index"] = tab_index + 1
237
+ data_bytes = json.dumps(tab).encode("utf-8")
238
+ file_storage.file_set(f"tab_{tab['id']}", data_bytes)
258
239
  file_storage.sync = True
259
240
  file_storage.push()
260
241
  # endregion
261
242
 
262
243
  # region modify name
263
- if name is not None and current_data['name'] != name:
264
- current_data['name'] = name
244
+ if name is not None and current_data["name"] != name:
245
+ current_data["name"] = name
265
246
  # endregion
266
247
 
267
248
  # region modify content
268
- if content is not None and current_data['content'] != content:
269
- current_data['content'] = content
249
+ if content is not None and current_data["content"] != content:
250
+ current_data["content"] = content
270
251
  # endregion
271
252
 
272
- data_bytes = json.dumps(current_data).encode('utf-8')
273
- file_storage.file_set(f'tab_{tab_id}', data_bytes)
253
+ data_bytes = json.dumps(current_data).encode("utf-8")
254
+ file_storage.file_set(f"tab_{tab_id}", data_bytes)
274
255
 
275
- return {
276
- 'id': current_data['id'],
277
- 'index': current_data['index'],
278
- 'name': current_data['name']
279
- }
256
+ return {"id": current_data["id"], "index": current_data["index"], "name": current_data["name"]}
280
257
 
281
258
  def delete(self, tab_id: int):
282
259
  file_storage = self._get_file_storage()
283
260
  try:
284
- file_storage.file_get(f'tab_{tab_id}')
285
- except FileNotFoundError:
286
- raise EntityNotExistsError(f'tab {tab_id}')
261
+ file_storage.file_get(f"tab_{tab_id}")
262
+ except FileNotFoundError as e:
263
+ raise EntityNotExistsError(f"tab {tab_id}") from e
287
264
 
288
- file_storage.delete(f'tab_{tab_id}')
265
+ file_storage.delete(f"tab_{tab_id}")
289
266
 
290
267
 
291
268
  tabs_controller = TabsController()
@@ -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,26 @@ 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
- name = name.lower()
18
-
19
17
  if project_name is None:
20
- project_name = config.get('default_project')
18
+ project_name = config.get("default_project")
21
19
  project_controller = ProjectController()
22
20
  project = project_controller.get(name=project_name)
23
21
 
24
22
  from mindsdb.api.executor.controllers.session_controller import SessionController
23
+
25
24
  session = SessionController()
26
25
 
27
26
  # check exists
28
27
  trigger = self.get_trigger_record(name, project_name)
29
28
  if trigger is not None:
30
- raise Exception(f'Trigger already exists: {name}')
29
+ raise Exception(f"Trigger already exists: {name}")
31
30
 
32
31
  # check table
33
32
  if len(table.parts) < 2:
34
- raise Exception(f'Database or table not found: {table}')
33
+ raise Exception(f"Database or table not found: {table}")
35
34
 
36
35
  table_name = Identifier(parts=table.parts[1:]).to_string()
37
36
  db_name = table.parts[0]
@@ -39,39 +38,38 @@ class TriggersController:
39
38
  db_integration = session.integration_controller.get(db_name)
40
39
  db_handler = session.integration_controller.get_data_handler(db_name)
41
40
 
42
- if not hasattr(db_handler, 'subscribe'):
43
- raise Exception(f'Handler {db_integration["engine"]} does''t support subscription')
41
+ if not hasattr(db_handler, "subscribe"):
42
+ raise Exception(f"Handler {db_integration['engine']} doest support subscription")
44
43
 
45
44
  df = db_handler.get_tables().data_frame
46
- column = 'table_name'
45
+ column = "table_name"
47
46
  if column not in df.columns:
48
47
  column = df.columns[0]
49
48
  tables = list(df[column])
50
49
 
51
50
  # check only if tables are visible
52
51
  if len(tables) > 0 and table_name not in tables:
53
- raise Exception(f'Table {table_name} not found in {db_name}')
52
+ raise Exception(f"Table {table_name} not found in {db_name}")
54
53
 
55
54
  columns_str = None
56
55
  if columns is not None and len(columns) > 0:
57
56
  # join to string with delimiter
58
- columns_str = '|'.join([col.parts[-1] for col in columns])
57
+ columns_str = "|".join([col.parts[-1] for col in columns])
59
58
 
60
59
  # check sql
61
60
  try:
62
61
  parse_sql(query_str)
63
62
  except ParsingException as e:
64
- raise ParsingException(f'Unable to parse: {query_str}: {e}')
63
+ raise ParsingException(f"Unable to parse: {query_str}: {e}") from e
65
64
 
66
65
  # create job record
67
66
  record = db.Triggers(
68
67
  name=name,
69
68
  project_id=project.id,
70
-
71
- database_id=db_integration['id'],
69
+ database_id=db_integration["id"],
72
70
  table_name=table_name,
73
71
  query_str=query_str,
74
- columns=columns_str
72
+ columns=columns_str,
75
73
  )
76
74
  db.session.add(record)
77
75
  db.session.flush()
@@ -79,7 +77,6 @@ class TriggersController:
79
77
  task_record = db.Tasks(
80
78
  company_id=ctx.company_id,
81
79
  user_class=ctx.user_class,
82
-
83
80
  object_type=self.OBJECT_TYPE,
84
81
  object_id=record.id,
85
82
  )
@@ -110,34 +107,36 @@ class TriggersController:
110
107
  project_controller = ProjectController()
111
108
  project = project_controller.get(name=project_name)
112
109
 
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,
110
+ query = (
111
+ db.session.query(db.Triggers)
112
+ .join(db.Tasks, db.Triggers.id == db.Tasks.object_id)
113
+ .filter(
114
+ db.Triggers.project_id == project.id,
115
+ db.Triggers.name == name,
116
+ db.Tasks.object_type == self.OBJECT_TYPE,
117
+ db.Tasks.company_id == ctx.company_id,
118
+ )
122
119
  )
123
120
  return query.first()
124
121
 
125
122
  def get_list(self, project_name=None):
126
123
  session = SessionController()
127
124
 
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)\
125
+ query = (
126
+ db.session.query(
127
+ db.Tasks.object_id,
128
+ db.Triggers.project_id,
129
+ db.Triggers.name,
130
+ db.Triggers.database_id,
131
+ db.Triggers.table_name,
132
+ db.Triggers.query_str,
133
+ db.Tasks.last_error,
134
+ )
135
+ .join(db.Triggers, db.Triggers.id == db.Tasks.object_id)
138
136
  .filter(
139
137
  db.Tasks.object_type == self.OBJECT_TYPE,
140
138
  db.Tasks.company_id == ctx.company_id,
139
+ )
141
140
  )
142
141
 
143
142
  project_controller = ProjectController()
@@ -145,24 +144,20 @@ class TriggersController:
145
144
  project = project_controller.get(name=project_name)
146
145
  query = query.filter(db.Triggers.project_id == project.id)
147
146
 
148
- database_names = {
149
- i['id']: i['name']
150
- for i in session.database_controller.get_list()
151
- }
147
+ database_names = {i["id"]: i["name"] for i in session.database_controller.get_list()}
152
148
 
153
- project_names = {
154
- i.id: i.name
155
- for i in project_controller.get_list()
156
- }
149
+ project_names = {i.id: i.name for i in project_controller.get_list()}
157
150
  data = []
158
151
  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
- })
152
+ data.append(
153
+ {
154
+ "id": record.object_id,
155
+ "project": project_names[record.project_id],
156
+ "name": record.name,
157
+ "database": database_names.get(record.database_id, "?"),
158
+ "table": record.table_name,
159
+ "query": record.query_str,
160
+ "last_error": record.last_error,
161
+ }
162
+ )
168
163
  return data