MindsDB 25.9.1.2__py3-none-any.whl → 25.9.3rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MindsDB might be problematic. Click here for more details.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +39 -20
- mindsdb/api/a2a/agent.py +7 -9
- mindsdb/api/a2a/common/server/server.py +3 -3
- mindsdb/api/a2a/common/server/task_manager.py +4 -4
- mindsdb/api/a2a/task_manager.py +15 -17
- mindsdb/api/common/middleware.py +9 -11
- mindsdb/api/executor/command_executor.py +2 -4
- mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +100 -48
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
- mindsdb/api/executor/datahub/datanodes/system_tables.py +1 -1
- mindsdb/api/executor/exceptions.py +29 -10
- mindsdb/api/executor/planner/plan_join.py +17 -3
- mindsdb/api/executor/sql_query/sql_query.py +74 -74
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +1 -2
- mindsdb/api/executor/sql_query/steps/subselect_step.py +0 -1
- mindsdb/api/executor/utilities/functions.py +6 -6
- mindsdb/api/executor/utilities/sql.py +32 -16
- mindsdb/api/http/gui.py +5 -11
- mindsdb/api/http/initialize.py +8 -10
- mindsdb/api/http/namespaces/agents.py +10 -12
- mindsdb/api/http/namespaces/analysis.py +13 -20
- mindsdb/api/http/namespaces/auth.py +1 -1
- mindsdb/api/http/namespaces/config.py +15 -11
- mindsdb/api/http/namespaces/databases.py +140 -201
- mindsdb/api/http/namespaces/file.py +15 -4
- mindsdb/api/http/namespaces/handlers.py +7 -2
- mindsdb/api/http/namespaces/knowledge_bases.py +8 -7
- mindsdb/api/http/namespaces/models.py +94 -126
- mindsdb/api/http/namespaces/projects.py +13 -22
- mindsdb/api/http/namespaces/sql.py +33 -25
- mindsdb/api/http/namespaces/tab.py +27 -37
- mindsdb/api/http/namespaces/views.py +1 -1
- mindsdb/api/http/start.py +14 -8
- mindsdb/api/mcp/__init__.py +2 -1
- mindsdb/api/mysql/mysql_proxy/executor/mysql_executor.py +15 -20
- mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +26 -50
- mindsdb/api/mysql/mysql_proxy/utilities/__init__.py +0 -1
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +6 -13
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +40 -28
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +168 -185
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +11 -5
- mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +1 -1
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +20 -2
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +18 -3
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +25 -12
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +2 -1
- mindsdb/integrations/handlers/statsforecast_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/statsforecast_handler/requirements_extra.txt +1 -0
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +4 -4
- mindsdb/integrations/libs/api_handler.py +10 -10
- mindsdb/integrations/libs/base.py +4 -4
- mindsdb/integrations/libs/llm/utils.py +2 -2
- mindsdb/integrations/libs/ml_handler_process/create_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/func_call_process.py +2 -7
- mindsdb/integrations/libs/ml_handler_process/learn_process.py +37 -47
- mindsdb/integrations/libs/ml_handler_process/update_engine_process.py +4 -7
- mindsdb/integrations/libs/ml_handler_process/update_process.py +2 -7
- mindsdb/integrations/libs/process_cache.py +132 -140
- mindsdb/integrations/libs/response.py +18 -12
- mindsdb/integrations/libs/vectordatabase_handler.py +26 -0
- mindsdb/integrations/utilities/files/file_reader.py +6 -7
- mindsdb/integrations/utilities/rag/config_loader.py +37 -26
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +59 -9
- mindsdb/integrations/utilities/rag/rerankers/reranker_compressor.py +4 -4
- mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +55 -133
- mindsdb/integrations/utilities/rag/settings.py +58 -133
- mindsdb/integrations/utilities/rag/splitters/file_splitter.py +5 -15
- mindsdb/interfaces/agents/agents_controller.py +2 -1
- mindsdb/interfaces/agents/constants.py +0 -2
- mindsdb/interfaces/agents/litellm_server.py +34 -58
- mindsdb/interfaces/agents/mcp_client_agent.py +10 -10
- mindsdb/interfaces/agents/mindsdb_database_agent.py +5 -5
- mindsdb/interfaces/agents/run_mcp_agent.py +12 -21
- mindsdb/interfaces/chatbot/chatbot_task.py +20 -23
- mindsdb/interfaces/chatbot/polling.py +30 -18
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +10 -10
- mindsdb/interfaces/database/integrations.py +19 -2
- mindsdb/interfaces/file/file_controller.py +6 -6
- mindsdb/interfaces/functions/controller.py +1 -1
- mindsdb/interfaces/functions/to_markdown.py +2 -2
- mindsdb/interfaces/jobs/jobs_controller.py +5 -5
- mindsdb/interfaces/jobs/scheduler.py +3 -8
- mindsdb/interfaces/knowledge_base/controller.py +54 -25
- mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
- mindsdb/interfaces/model/model_controller.py +170 -166
- mindsdb/interfaces/query_context/context_controller.py +14 -2
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +6 -4
- mindsdb/interfaces/skills/retrieval_tool.py +43 -50
- mindsdb/interfaces/skills/skill_tool.py +2 -2
- mindsdb/interfaces/skills/sql_agent.py +25 -19
- mindsdb/interfaces/storage/fs.py +114 -169
- mindsdb/interfaces/storage/json.py +19 -18
- mindsdb/interfaces/storage/model_fs.py +54 -92
- mindsdb/interfaces/tabs/tabs_controller.py +49 -72
- mindsdb/interfaces/tasks/task_monitor.py +3 -9
- mindsdb/interfaces/tasks/task_thread.py +7 -9
- mindsdb/interfaces/triggers/trigger_task.py +7 -13
- mindsdb/interfaces/triggers/triggers_controller.py +47 -50
- mindsdb/migrations/migrate.py +16 -16
- mindsdb/utilities/api_status.py +58 -0
- mindsdb/utilities/config.py +49 -0
- mindsdb/utilities/exception.py +40 -1
- mindsdb/utilities/fs.py +0 -1
- mindsdb/utilities/hooks/profiling.py +17 -14
- mindsdb/utilities/langfuse.py +40 -45
- mindsdb/utilities/log.py +272 -0
- mindsdb/utilities/ml_task_queue/consumer.py +52 -58
- mindsdb/utilities/ml_task_queue/producer.py +26 -30
- mindsdb/utilities/render/sqlalchemy_render.py +8 -7
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/METADATA +266 -261
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/RECORD +119 -119
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.1.2.dist-info → mindsdb-25.9.3rc1.dist-info}/top_level.txt +0 -0
|
@@ -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 =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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 =
|
|
56
|
-
|
|
57
|
-
resource_id=self.resource_id,
|
|
58
|
-
|
|
59
|
-
)
|
|
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
|
|
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.
|
|
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.
|
|
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(
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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[
|
|
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
|
|
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
|
-
"""
|
|
163
|
+
"""check if storage directory is empty
|
|
187
164
|
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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(
|
|
342
|
-
record_dict[
|
|
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[
|
|
362
|
-
encrypted_json_storage.set_str(record[
|
|
318
|
+
if record["encrypted_content"]:
|
|
319
|
+
encrypted_json_storage.set_str(record["name"], record["encrypted_content"])
|
|
363
320
|
else:
|
|
364
|
-
json_storage.set(record[
|
|
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 =
|
|
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(
|
|
66
|
+
if (child.is_file() and child.name.startswith("tab_")) is False:
|
|
73
67
|
continue
|
|
74
|
-
tab_id = child.name.replace(
|
|
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[
|
|
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(
|
|
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[
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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[
|
|
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(
|
|
158
|
+
raise ValueError("Tab id must be integer")
|
|
171
159
|
|
|
172
160
|
try:
|
|
173
|
-
raw_tab_data = self._get_file_storage().file_get(f
|
|
174
|
-
except FileNotFoundError:
|
|
175
|
-
raise EntityNotExistsError(f
|
|
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 =
|
|
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(
|
|
193
|
+
index = max([x.get("index", 0) for x in all_tabs]) + 1
|
|
209
194
|
|
|
210
|
-
data_bytes = json.dumps({
|
|
211
|
-
|
|
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[
|
|
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[
|
|
223
|
-
data_bytes = json.dumps(tab).encode(
|
|
224
|
-
file_storage.file_set(f
|
|
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 {
|
|
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[
|
|
247
|
-
current_data[
|
|
248
|
-
all_tabs = [x for x in self.get_all() if x[
|
|
249
|
-
all_tabs.sort(key=lambda x: x[
|
|
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[
|
|
234
|
+
tab["index"] = tab_index
|
|
254
235
|
else:
|
|
255
|
-
tab[
|
|
256
|
-
data_bytes = json.dumps(tab).encode(
|
|
257
|
-
file_storage.file_set(f
|
|
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[
|
|
264
|
-
current_data[
|
|
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[
|
|
269
|
-
current_data[
|
|
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(
|
|
273
|
-
file_storage.file_set(f
|
|
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
|
|
285
|
-
except FileNotFoundError:
|
|
286
|
-
raise EntityNotExistsError(f
|
|
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
|
|
265
|
+
file_storage.delete(f"tab_{tab_id}")
|
|
289
266
|
|
|
290
267
|
|
|
291
268
|
tabs_controller = TabsController()
|