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
@@ -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,18 +11,16 @@ from .fs import RESOURCE_GROUP, FileStorageFactory, SERVICE_FILES_NAMES
11
11
  from .json import get_json_storage, get_encrypted_json_storage
12
12
 
13
13
 
14
- JSON_STORAGE_FILE = 'json_storage.json'
14
+ JSON_STORAGE_FILE = "json_storage.json"
15
15
 
16
16
 
17
17
  class ModelStorage:
18
18
  """
19
19
  This class deals with all model-related storage requirements, from setting status to storing artifacts.
20
20
  """
21
+
21
22
  def __init__(self, predictor_id):
22
- storageFactory = FileStorageFactory(
23
- resource_group=RESOURCE_GROUP.PREDICTOR,
24
- sync=True
25
- )
23
+ storageFactory = FileStorageFactory(resource_group=RESOURCE_GROUP.PREDICTOR, sync=True)
26
24
  self.fileStorage = storageFactory(predictor_id)
27
25
  self.predictor_id = predictor_id
28
26
 
@@ -43,15 +41,12 @@ class ModelStorage:
43
41
  """
44
42
  model_record = db.Predictor.query.get(self.predictor_id)
45
43
  if check_exists is True and model_record is None:
46
- raise KeyError('Model does not exists')
44
+ raise KeyError("Model does not exists")
47
45
  return model_record
48
46
 
49
47
  def get_info(self):
50
48
  rec = self._get_model_record(self.predictor_id)
51
- return dict(status=rec.status,
52
- to_predict=rec.to_predict,
53
- data=rec.data,
54
- learn_args=rec.learn_args)
49
+ return dict(status=rec.status, to_predict=rec.to_predict, data=rec.data, learn_args=rec.learn_args)
55
50
 
56
51
  def status_set(self, status, status_info=None):
57
52
  rec = self._get_model_record(self.predictor_id)
@@ -95,67 +90,52 @@ class ModelStorage:
95
90
 
96
91
  def folder_get(self, name):
97
92
  # pull folder and return path
98
- name = name.lower().replace(' ', '_')
99
- name = re.sub(r'([^a-z^A-Z^_\d]+)', '_', name)
93
+ name = name.lower().replace(" ", "_")
94
+ name = re.sub(r"([^a-z^A-Z^_\d]+)", "_", name)
100
95
 
101
96
  self.fileStorage.pull_path(name)
102
97
  return str(self.fileStorage.get_path(name))
103
98
 
104
99
  def folder_sync(self, name):
105
100
  # sync abs path
106
- name = name.lower().replace(' ', '_')
107
- name = re.sub(r'([^a-z^A-Z^_\d]+)', '_', name)
101
+ name = name.lower().replace(" ", "_")
102
+ name = re.sub(r"([^a-z^A-Z^_\d]+)", "_", name)
108
103
 
109
104
  self.fileStorage.push_path(name)
110
105
 
111
- def file_list(self):
112
- ...
106
+ def file_list(self): ...
113
107
 
114
- def file_del(self, name):
115
- ...
108
+ def file_del(self, name): ...
116
109
 
117
110
  # jsons
118
111
 
119
112
  def json_set(self, name, data):
120
- json_storage = get_json_storage(
121
- resource_id=self.predictor_id,
122
- resource_group=RESOURCE_GROUP.PREDICTOR
123
- )
113
+ json_storage = get_json_storage(resource_id=self.predictor_id, resource_group=RESOURCE_GROUP.PREDICTOR)
124
114
  return json_storage.set(name, data)
125
115
 
126
116
  def encrypted_json_set(self, name: str, data: dict) -> None:
127
117
  json_storage = get_encrypted_json_storage(
128
- resource_id=self.predictor_id,
129
- resource_group=RESOURCE_GROUP.PREDICTOR
118
+ resource_id=self.predictor_id, resource_group=RESOURCE_GROUP.PREDICTOR
130
119
  )
131
120
  return json_storage.set(name, data)
132
121
 
133
122
  def json_get(self, name):
134
- json_storage = get_json_storage(
135
- resource_id=self.predictor_id,
136
- resource_group=RESOURCE_GROUP.PREDICTOR
137
- )
123
+ json_storage = get_json_storage(resource_id=self.predictor_id, resource_group=RESOURCE_GROUP.PREDICTOR)
138
124
  return json_storage.get(name)
139
125
 
140
126
  def encrypted_json_get(self, name: str) -> dict:
141
127
  json_storage = get_encrypted_json_storage(
142
- resource_id=self.predictor_id,
143
- resource_group=RESOURCE_GROUP.PREDICTOR
128
+ resource_id=self.predictor_id, resource_group=RESOURCE_GROUP.PREDICTOR
144
129
  )
145
130
  return json_storage.get(name)
146
131
 
147
- def json_list(self):
148
- ...
132
+ def json_list(self): ...
149
133
 
150
- def json_del(self, name):
151
- ...
134
+ def json_del(self, name): ...
152
135
 
153
136
  def delete(self):
154
137
  self.fileStorage.delete()
155
- json_storage = get_json_storage(
156
- resource_id=self.predictor_id,
157
- resource_group=RESOURCE_GROUP.PREDICTOR
158
- )
138
+ json_storage = get_json_storage(resource_id=self.predictor_id, resource_group=RESOURCE_GROUP.PREDICTOR)
159
139
  json_storage.clean()
160
140
 
161
141
 
@@ -164,29 +144,26 @@ class HandlerStorage:
164
144
  This class deals with all handler-related storage requirements, from storing metadata to synchronizing folders
165
145
  across instances.
166
146
  """
147
+
167
148
  def __init__(self, integration_id: int, root_dir: str = None, is_temporal=False):
168
149
  args = {}
169
150
  if root_dir is not None:
170
- args['root_dir'] = root_dir
171
- storageFactory = FileStorageFactory(
172
- resource_group=RESOURCE_GROUP.INTEGRATION,
173
- sync=False,
174
- **args
175
- )
151
+ args["root_dir"] = root_dir
152
+ storageFactory = FileStorageFactory(resource_group=RESOURCE_GROUP.INTEGRATION, sync=False, **args)
176
153
  self.fileStorage = storageFactory(integration_id)
177
154
  self.integration_id = integration_id
178
155
  self.is_temporal = is_temporal
179
156
  # do not sync with remote storage
180
157
 
181
158
  def __convert_name(self, name):
182
- name = name.lower().replace(' ', '_')
183
- return re.sub(r'([^a-z^A-Z^_\d]+)', '_', name)
159
+ name = name.lower().replace(" ", "_")
160
+ return re.sub(r"([^a-z^A-Z^_\d]+)", "_", name)
184
161
 
185
162
  def is_empty(self):
186
- """ check if storage directory is empty
163
+ """check if storage directory is empty
187
164
 
188
- Returns:
189
- bool: true if dir is empty
165
+ Returns:
166
+ bool: true if dir is empty
190
167
  """
191
168
  for path in self.fileStorage.folder_path.iterdir():
192
169
  if path.is_file() and path.name in SERVICE_FILES_NAMES:
@@ -221,19 +198,17 @@ class HandlerStorage:
221
198
  if not self.is_temporal:
222
199
  self.fileStorage.push_path(name)
223
200
 
224
- def file_list(self):
225
- ...
201
+ def file_list(self): ...
226
202
 
227
- def file_del(self, name):
228
- ...
203
+ def file_del(self, name): ...
229
204
 
230
205
  # folder
231
206
 
232
207
  def folder_get(self, name):
233
- ''' Copies folder from remote to local file system and returns its path
208
+ """Copies folder from remote to local file system and returns its path
234
209
 
235
210
  :param name: name of the folder
236
- '''
211
+ """
237
212
  name = self.__convert_name(name)
238
213
 
239
214
  self.fileStorage.pull_path(name)
@@ -249,38 +224,28 @@ class HandlerStorage:
249
224
  # jsons
250
225
 
251
226
  def json_set(self, name, content):
252
- json_storage = get_json_storage(
253
- resource_id=self.integration_id,
254
- resource_group=RESOURCE_GROUP.INTEGRATION
255
- )
227
+ json_storage = get_json_storage(resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION)
256
228
  return json_storage.set(name, content)
257
229
 
258
230
  def encrypted_json_set(self, name: str, content: dict) -> None:
259
231
  json_storage = get_encrypted_json_storage(
260
- resource_id=self.integration_id,
261
- resource_group=RESOURCE_GROUP.INTEGRATION
232
+ resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION
262
233
  )
263
234
  return json_storage.set(name, content)
264
235
 
265
236
  def json_get(self, name):
266
- json_storage = get_json_storage(
267
- resource_id=self.integration_id,
268
- resource_group=RESOURCE_GROUP.INTEGRATION
269
- )
237
+ json_storage = get_json_storage(resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION)
270
238
  return json_storage.get(name)
271
239
 
272
240
  def encrypted_json_get(self, name: str) -> dict:
273
241
  json_storage = get_encrypted_json_storage(
274
- resource_id=self.integration_id,
275
- resource_group=RESOURCE_GROUP.INTEGRATION
242
+ resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION
276
243
  )
277
244
  return json_storage.get(name)
278
245
 
279
- def json_list(self):
280
- ...
246
+ def json_list(self): ...
281
247
 
282
- def json_del(self, name):
283
- ...
248
+ def json_del(self, name): ...
284
249
 
285
250
  def export_files(self) -> bytes:
286
251
  json_storage = self.export_json_storage()
@@ -288,11 +253,11 @@ class HandlerStorage:
288
253
  if self.is_empty() and not json_storage:
289
254
  return None
290
255
 
291
- folder_path = self.folder_get('')
256
+ folder_path = self.folder_get("")
292
257
 
293
258
  zip_fd = io.BytesIO()
294
259
 
295
- with zipfile.ZipFile(zip_fd, 'w', zipfile.ZIP_DEFLATED) as zipf:
260
+ with zipfile.ZipFile(zip_fd, "w", zipfile.ZIP_DEFLATED) as zipf:
296
261
  for root, dirs, files in os.walk(folder_path):
297
262
  for file_name in files:
298
263
  if file_name in SERVICE_FILES_NAMES:
@@ -309,14 +274,13 @@ class HandlerStorage:
309
274
  return zip_fd.read()
310
275
 
311
276
  def import_files(self, content: bytes):
312
-
313
- folder_path = self.folder_get('')
277
+ folder_path = self.folder_get("")
314
278
 
315
279
  zip_fd = io.BytesIO()
316
280
  zip_fd.write(content)
317
281
  zip_fd.seek(0)
318
282
 
319
- with zipfile.ZipFile(zip_fd, 'r') as zip_ref:
283
+ with zipfile.ZipFile(zip_fd, "r") as zip_ref:
320
284
  for name in zip_ref.namelist():
321
285
  # If JSON storage file is in the zip file, import the content to the JSON storage.
322
286
  # Thereafter, remove the file from the folder.
@@ -327,38 +291,36 @@ class HandlerStorage:
327
291
  else:
328
292
  zip_ref.extract(name, folder_path)
329
293
 
330
- self.folder_sync('')
294
+ self.folder_sync("")
331
295
 
332
296
  def export_json_storage(self) -> list[dict]:
333
- json_storage = get_json_storage(
334
- resource_id=self.integration_id,
335
- resource_group=RESOURCE_GROUP.INTEGRATION
336
- )
297
+ json_storage = get_json_storage(resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION)
337
298
 
338
299
  records = []
339
300
  for record in json_storage.get_all_records():
340
301
  record_dict = record.to_dict()
341
- if record_dict.get('encrypted_content'):
342
- record_dict['encrypted_content'] = record_dict['encrypted_content'].decode()
302
+ if record_dict.get("encrypted_content"):
303
+ record_dict["encrypted_content"] = record_dict["encrypted_content"].decode()
343
304
  records.append(record_dict)
344
305
 
345
306
  return records
346
307
 
347
308
  def import_json_storage(self, records: bytes) -> None:
348
- json_storage = get_json_storage(
349
- resource_id=self.integration_id,
350
- resource_group=RESOURCE_GROUP.INTEGRATION
351
- )
309
+ json_storage = get_json_storage(resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION)
352
310
 
353
311
  encrypted_json_storage = get_encrypted_json_storage(
354
- resource_id=self.integration_id,
355
- resource_group=RESOURCE_GROUP.INTEGRATION
312
+ resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION
356
313
  )
357
314
 
358
315
  records = json.loads(records.decode())
359
316
 
360
317
  for record in records:
361
- if record['encrypted_content']:
362
- encrypted_json_storage.set_str(record['name'], record['encrypted_content'])
318
+ if record["encrypted_content"]:
319
+ encrypted_json_storage.set_str(record["name"], record["encrypted_content"])
363
320
  else:
364
- json_storage.set(record['name'], record['content'])
321
+ json_storage.set(record["name"], record["content"])
322
+
323
+ def delete(self):
324
+ self.fileStorage.delete()
325
+ json_storage = get_json_storage(resource_id=self.integration_id, resource_group=RESOURCE_GROUP.INTEGRATION)
326
+ json_storage.clean()
@@ -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()