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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +40 -29
- mindsdb/api/a2a/__init__.py +1 -1
- mindsdb/api/a2a/agent.py +16 -10
- mindsdb/api/a2a/common/server/server.py +7 -3
- mindsdb/api/a2a/common/server/task_manager.py +12 -5
- mindsdb/api/a2a/common/types.py +66 -0
- mindsdb/api/a2a/task_manager.py +65 -17
- mindsdb/api/common/middleware.py +10 -12
- mindsdb/api/executor/command_executor.py +51 -40
- mindsdb/api/executor/datahub/datanodes/datanode.py +2 -2
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +7 -13
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +101 -49
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +8 -4
- mindsdb/api/executor/datahub/datanodes/system_tables.py +3 -2
- mindsdb/api/executor/exceptions.py +29 -10
- mindsdb/api/executor/planner/plan_join.py +17 -3
- mindsdb/api/executor/planner/query_prepare.py +2 -20
- 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 +37 -20
- mindsdb/api/http/gui.py +5 -11
- mindsdb/api/http/initialize.py +75 -61
- mindsdb/api/http/namespaces/agents.py +10 -15
- mindsdb/api/http/namespaces/analysis.py +13 -20
- mindsdb/api/http/namespaces/auth.py +1 -1
- mindsdb/api/http/namespaces/chatbots.py +0 -5
- mindsdb/api/http/namespaces/config.py +15 -11
- mindsdb/api/http/namespaces/databases.py +140 -201
- mindsdb/api/http/namespaces/file.py +17 -4
- mindsdb/api/http/namespaces/handlers.py +17 -7
- mindsdb/api/http/namespaces/knowledge_bases.py +28 -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 +16 -10
- 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/mysql/mysql_proxy/utilities/dump.py +8 -2
- mindsdb/integrations/handlers/byom_handler/byom_handler.py +165 -190
- mindsdb/integrations/handlers/databricks_handler/databricks_handler.py +98 -46
- mindsdb/integrations/handlers/druid_handler/druid_handler.py +32 -40
- mindsdb/integrations/handlers/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/gitlab_handler/gitlab_handler.py +5 -2
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +438 -100
- mindsdb/integrations/handlers/mssql_handler/requirements_odbc.txt +3 -0
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +235 -3
- mindsdb/integrations/handlers/oracle_handler/__init__.py +2 -0
- mindsdb/integrations/handlers/oracle_handler/connection_args.py +7 -1
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +321 -16
- mindsdb/integrations/handlers/oracle_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +14 -2
- mindsdb/integrations/handlers/shopify_handler/requirements.txt +1 -0
- mindsdb/integrations/handlers/shopify_handler/shopify_handler.py +80 -13
- 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/handlers/zendesk_handler/zendesk_tables.py +144 -111
- 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/handlers/auth_utilities/snowflake/__init__.py +1 -0
- mindsdb/integrations/utilities/handlers/auth_utilities/snowflake/snowflake_jwt_gen.py +151 -0
- mindsdb/integrations/utilities/rag/config_loader.py +37 -26
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +83 -30
- 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 -3
- 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 +16 -17
- mindsdb/interfaces/data_catalog/data_catalog_reader.py +15 -4
- mindsdb/interfaces/database/data_handlers_cache.py +190 -0
- mindsdb/interfaces/database/database.py +3 -3
- mindsdb/interfaces/database/integrations.py +7 -110
- mindsdb/interfaces/database/projects.py +2 -6
- mindsdb/interfaces/database/views.py +1 -4
- 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 -9
- mindsdb/interfaces/jobs/scheduler.py +3 -9
- mindsdb/interfaces/knowledge_base/controller.py +244 -128
- mindsdb/interfaces/knowledge_base/evaluate.py +36 -41
- mindsdb/interfaces/knowledge_base/executor.py +11 -0
- mindsdb/interfaces/knowledge_base/llm_client.py +51 -17
- mindsdb/interfaces/knowledge_base/preprocessing/json_chunker.py +40 -61
- mindsdb/interfaces/model/model_controller.py +172 -168
- mindsdb/interfaces/query_context/context_controller.py +14 -2
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +10 -14
- mindsdb/interfaces/skills/retrieval_tool.py +43 -50
- mindsdb/interfaces/skills/skill_tool.py +2 -2
- mindsdb/interfaces/skills/skills_controller.py +1 -4
- mindsdb/interfaces/skills/sql_agent.py +25 -19
- mindsdb/interfaces/storage/db.py +16 -6
- mindsdb/interfaces/storage/fs.py +114 -169
- mindsdb/interfaces/storage/json.py +19 -18
- 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 -52
- mindsdb/migrations/migrate.py +16 -16
- mindsdb/utilities/api_status.py +58 -0
- mindsdb/utilities/config.py +68 -2
- mindsdb/utilities/exception.py +40 -1
- mindsdb/utilities/fs.py +0 -1
- mindsdb/utilities/hooks/profiling.py +17 -14
- mindsdb/utilities/json_encoder.py +24 -10
- 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 +22 -20
- mindsdb/utilities/starters.py +0 -10
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/METADATA +286 -267
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/RECORD +145 -159
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- mindsdb/api/postgres/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/executor/__init__.py +0 -1
- mindsdb/api/postgres/postgres_proxy/executor/executor.py +0 -189
- mindsdb/api/postgres/postgres_proxy/postgres_packets/__init__.py +0 -0
- mindsdb/api/postgres/postgres_proxy/postgres_packets/errors.py +0 -322
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_fields.py +0 -34
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_formats.py +0 -1265
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_message_identifiers.py +0 -31
- mindsdb/api/postgres/postgres_proxy/postgres_packets/postgres_packets.py +0 -253
- mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +0 -477
- mindsdb/api/postgres/postgres_proxy/utilities/__init__.py +0 -10
- mindsdb/api/postgres/start.py +0 -11
- mindsdb/integrations/handlers/mssql_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/mssql_handler/tests/test_mssql_handler.py +0 -169
- mindsdb/integrations/handlers/oracle_handler/tests/__init__.py +0 -0
- mindsdb/integrations/handlers/oracle_handler/tests/test_oracle_handler.py +0 -32
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.10.0.dist-info}/licenses/LICENSE +0 -0
- {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 =
|
|
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,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()
|
|
@@ -16,7 +16,6 @@ logger = log.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TaskMonitor:
|
|
19
|
-
|
|
20
19
|
MONITOR_INTERVAL_SECONDS = 2
|
|
21
20
|
LOCK_EXPIRED_SECONDS = MONITOR_INTERVAL_SECONDS * 30
|
|
22
21
|
|
|
@@ -39,15 +38,14 @@ class TaskMonitor:
|
|
|
39
38
|
self.stop_all_tasks()
|
|
40
39
|
return
|
|
41
40
|
|
|
42
|
-
except Exception
|
|
43
|
-
logger.
|
|
41
|
+
except Exception:
|
|
42
|
+
logger.exception("Error in TaskMonitor.start")
|
|
44
43
|
db.session.rollback()
|
|
45
44
|
|
|
46
45
|
if stop_event is not None and stop_event.is_set():
|
|
47
46
|
return
|
|
48
47
|
|
|
49
48
|
def stop_all_tasks(self):
|
|
50
|
-
|
|
51
49
|
active_tasks = list(self._active_tasks.keys())
|
|
52
50
|
for task_id in active_tasks:
|
|
53
51
|
self.stop_task(task_id)
|
|
@@ -65,7 +63,6 @@ class TaskMonitor:
|
|
|
65
63
|
# Check active tasks
|
|
66
64
|
active_tasks = list(self._active_tasks.items())
|
|
67
65
|
for task_id, task in active_tasks:
|
|
68
|
-
|
|
69
66
|
if task_id not in allowed_tasks:
|
|
70
67
|
# old task
|
|
71
68
|
self.stop_task(task_id)
|
|
@@ -96,9 +93,7 @@ class TaskMonitor:
|
|
|
96
93
|
task.run_by = run_by
|
|
97
94
|
task.alive_time = db_date
|
|
98
95
|
|
|
99
|
-
elif db_date - task.alive_time > dt.timedelta(
|
|
100
|
-
seconds=self.LOCK_EXPIRED_SECONDS
|
|
101
|
-
):
|
|
96
|
+
elif db_date - task.alive_time > dt.timedelta(seconds=self.LOCK_EXPIRED_SECONDS):
|
|
102
97
|
# lock expired
|
|
103
98
|
task.run_by = run_by
|
|
104
99
|
task.alive_time = db_date
|
|
@@ -145,7 +140,6 @@ class TaskMonitor:
|
|
|
145
140
|
|
|
146
141
|
|
|
147
142
|
def start(verbose=False):
|
|
148
|
-
|
|
149
143
|
monitor = TaskMonitor()
|
|
150
144
|
monitor.start()
|
|
151
145
|
|
|
@@ -12,7 +12,6 @@ logger = log.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class TaskThread(threading.Thread):
|
|
15
|
-
|
|
16
15
|
def __init__(self, task_id):
|
|
17
16
|
threading.Thread.__init__(self)
|
|
18
17
|
self.task_id = task_id
|
|
@@ -34,28 +33,27 @@ class TaskThread(threading.Thread):
|
|
|
34
33
|
self.object_type = task_record.object_type
|
|
35
34
|
self.object_id = task_record.object_id
|
|
36
35
|
|
|
37
|
-
logger.info(f
|
|
36
|
+
logger.info(f"Task starting: {self.object_type}.{self.object_id}")
|
|
38
37
|
try:
|
|
39
|
-
if self.object_type ==
|
|
40
|
-
|
|
38
|
+
if self.object_type == "trigger":
|
|
41
39
|
trigger = TriggerTask(self.task_id, self.object_id)
|
|
42
40
|
trigger.run(self._stop_event)
|
|
43
41
|
|
|
44
|
-
elif self.object_type ==
|
|
42
|
+
elif self.object_type == "chatbot":
|
|
45
43
|
bot = ChatBotTask(self.task_id, self.object_id)
|
|
46
44
|
bot.run(self._stop_event)
|
|
47
45
|
|
|
48
|
-
elif self.object_type ==
|
|
46
|
+
elif self.object_type == "query":
|
|
49
47
|
query = QueryTask(self.task_id, self.object_id)
|
|
50
48
|
query.run(self._stop_event)
|
|
51
49
|
|
|
52
50
|
except Exception:
|
|
53
|
-
logger.
|
|
54
|
-
task_record.last_error =
|
|
51
|
+
logger.exception("Error during task processing:")
|
|
52
|
+
task_record.last_error = traceback.format_exc()
|
|
55
53
|
|
|
56
54
|
db.session.commit()
|
|
57
55
|
|
|
58
56
|
def stop(self):
|
|
59
|
-
logger.info(f
|
|
57
|
+
logger.info(f"Task stopping: {self.object_type}.{self.object_id}")
|
|
60
58
|
|
|
61
59
|
self._stop_event.set()
|
|
@@ -44,19 +44,19 @@ class TriggerTask(BaseTask):
|
|
|
44
44
|
|
|
45
45
|
# subscribe
|
|
46
46
|
database = session.integration_controller.get_by_id(trigger.database_id)
|
|
47
|
-
data_handler = session.integration_controller.get_data_handler(database[
|
|
47
|
+
data_handler = session.integration_controller.get_data_handler(database["name"])
|
|
48
48
|
|
|
49
49
|
columns = trigger.columns
|
|
50
50
|
if columns is not None:
|
|
51
|
-
if columns ==
|
|
51
|
+
if columns == "":
|
|
52
52
|
columns = None
|
|
53
53
|
else:
|
|
54
|
-
columns = columns.split(
|
|
54
|
+
columns = columns.split("|")
|
|
55
55
|
|
|
56
56
|
data_handler.subscribe(stop_event, self._callback, trigger.table_name, columns=columns)
|
|
57
57
|
|
|
58
58
|
def _callback(self, row, key=None):
|
|
59
|
-
logger.debug(f
|
|
59
|
+
logger.debug(f"trigger call: {row}, {key}")
|
|
60
60
|
|
|
61
61
|
# set up environment
|
|
62
62
|
ctx.load(self._ctx_dump)
|
|
@@ -64,21 +64,14 @@ class TriggerTask(BaseTask):
|
|
|
64
64
|
try:
|
|
65
65
|
if key is not None:
|
|
66
66
|
row.update(key)
|
|
67
|
-
table = [
|
|
68
|
-
row
|
|
69
|
-
]
|
|
67
|
+
table = [row]
|
|
70
68
|
|
|
71
69
|
# inject data to query
|
|
72
70
|
query = copy.deepcopy(self.query)
|
|
73
71
|
|
|
74
72
|
def find_table(node, is_table, **kwargs):
|
|
75
|
-
|
|
76
73
|
if is_table:
|
|
77
|
-
if (
|
|
78
|
-
isinstance(node, Identifier)
|
|
79
|
-
and len(node.parts) == 1
|
|
80
|
-
and node.parts[0] == 'TABLE_DELTA'
|
|
81
|
-
):
|
|
74
|
+
if isinstance(node, Identifier) and len(node.parts) == 1 and node.parts[0] == "TABLE_DELTA":
|
|
82
75
|
# replace with data
|
|
83
76
|
return Data(table, alias=node.alias)
|
|
84
77
|
|
|
@@ -90,6 +83,7 @@ class TriggerTask(BaseTask):
|
|
|
90
83
|
self.set_error(ret.error_message)
|
|
91
84
|
|
|
92
85
|
except Exception:
|
|
86
|
+
logger.exception("Error during trigger call processing")
|
|
93
87
|
self.set_error(str(traceback.format_exc()))
|
|
94
88
|
|
|
95
89
|
db.session.commit()
|
|
@@ -11,27 +11,26 @@ from mindsdb.api.executor.controllers.session_controller import SessionControlle
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class TriggersController:
|
|
14
|
-
OBJECT_TYPE =
|
|
14
|
+
OBJECT_TYPE = "trigger"
|
|
15
15
|
|
|
16
16
|
def add(self, name, project_name, table, query_str, columns=None):
|
|
17
|
-
name = name.lower()
|
|
18
|
-
|
|
19
17
|
if project_name is None:
|
|
20
|
-
project_name = config.get(
|
|
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
|
|
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
|
|
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,
|
|
43
|
-
raise Exception(f
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
114
|
-
db.Triggers
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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 =
|
|
129
|
-
db.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|