MindsDB 25.5.4.2__py3-none-any.whl → 25.6.3.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/api/a2a/agent.py +50 -26
- mindsdb/api/a2a/common/server/server.py +32 -26
- mindsdb/api/a2a/task_manager.py +68 -6
- mindsdb/api/executor/command_executor.py +69 -14
- mindsdb/api/executor/datahub/datanodes/integration_datanode.py +49 -65
- mindsdb/api/executor/datahub/datanodes/mindsdb_tables.py +91 -84
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +29 -48
- mindsdb/api/executor/datahub/datanodes/system_tables.py +35 -61
- mindsdb/api/executor/planner/plan_join.py +67 -77
- mindsdb/api/executor/planner/query_planner.py +176 -155
- mindsdb/api/executor/planner/steps.py +37 -12
- mindsdb/api/executor/sql_query/result_set.py +45 -64
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +14 -18
- mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +17 -18
- mindsdb/api/executor/sql_query/steps/insert_step.py +13 -33
- mindsdb/api/executor/sql_query/steps/subselect_step.py +43 -35
- mindsdb/api/executor/utilities/sql.py +42 -48
- mindsdb/api/http/namespaces/config.py +1 -1
- mindsdb/api/http/namespaces/file.py +14 -23
- mindsdb/api/http/namespaces/knowledge_bases.py +132 -154
- mindsdb/api/mysql/mysql_proxy/data_types/mysql_datum.py +12 -28
- mindsdb/api/mysql/mysql_proxy/data_types/mysql_packets/binary_resultset_row_package.py +59 -50
- mindsdb/api/mysql/mysql_proxy/data_types/mysql_packets/resultset_row_package.py +9 -8
- mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +449 -461
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +87 -36
- mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +219 -28
- mindsdb/integrations/handlers/file_handler/file_handler.py +15 -9
- mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +43 -24
- mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +10 -3
- mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +29 -33
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +277 -356
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +74 -51
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +305 -98
- mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +145 -40
- mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +136 -6
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +352 -83
- mindsdb/integrations/libs/api_handler.py +279 -57
- mindsdb/integrations/libs/base.py +185 -30
- mindsdb/integrations/utilities/files/file_reader.py +99 -73
- mindsdb/integrations/utilities/handler_utils.py +23 -8
- mindsdb/integrations/utilities/sql_utils.py +35 -40
- mindsdb/interfaces/agents/agents_controller.py +226 -196
- mindsdb/interfaces/agents/constants.py +8 -1
- mindsdb/interfaces/agents/langchain_agent.py +42 -11
- mindsdb/interfaces/agents/mcp_client_agent.py +29 -21
- mindsdb/interfaces/agents/mindsdb_database_agent.py +23 -18
- mindsdb/interfaces/data_catalog/__init__.py +0 -0
- mindsdb/interfaces/data_catalog/base_data_catalog.py +54 -0
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +375 -0
- mindsdb/interfaces/data_catalog/data_catalog_reader.py +38 -0
- mindsdb/interfaces/database/database.py +81 -57
- mindsdb/interfaces/database/integrations.py +222 -234
- mindsdb/interfaces/database/log.py +72 -104
- mindsdb/interfaces/database/projects.py +156 -193
- mindsdb/interfaces/file/file_controller.py +21 -65
- mindsdb/interfaces/knowledge_base/controller.py +66 -25
- mindsdb/interfaces/knowledge_base/evaluate.py +516 -0
- mindsdb/interfaces/knowledge_base/llm_client.py +75 -0
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_kb_tools.py +83 -43
- mindsdb/interfaces/skills/skills_controller.py +31 -36
- mindsdb/interfaces/skills/sql_agent.py +113 -86
- mindsdb/interfaces/storage/db.py +242 -82
- mindsdb/migrations/versions/2025-05-28_a44643042fe8_added_data_catalog_tables.py +118 -0
- mindsdb/migrations/versions/2025-06-09_608e376c19a7_updated_data_catalog_data_types.py +58 -0
- mindsdb/utilities/config.py +13 -2
- mindsdb/utilities/log.py +35 -26
- mindsdb/utilities/ml_task_queue/task.py +19 -22
- mindsdb/utilities/render/sqlalchemy_render.py +129 -181
- mindsdb/utilities/starters.py +40 -0
- {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/METADATA +257 -257
- {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/RECORD +76 -68
- {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.5.4.2.dist-info → mindsdb-25.6.3.0.dist-info}/top_level.txt +0 -0
|
@@ -32,19 +32,19 @@ from mindsdb.utilities import log
|
|
|
32
32
|
from mindsdb.integrations.libs.ml_exec_base import BaseMLEngineExec
|
|
33
33
|
from mindsdb.integrations.libs.base import BaseHandler
|
|
34
34
|
import mindsdb.utilities.profiler as profiler
|
|
35
|
+
from mindsdb.interfaces.data_catalog.data_catalog_loader import DataCatalogLoader
|
|
35
36
|
|
|
36
37
|
logger = log.getLogger(__name__)
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
class HandlersCache:
|
|
40
|
-
"""
|
|
41
|
-
"""
|
|
41
|
+
"""Cache for data handlers that keep connections opened during ttl time from handler last use"""
|
|
42
42
|
|
|
43
43
|
def __init__(self, ttl: int = 60):
|
|
44
|
-
"""
|
|
44
|
+
"""init cache
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
Args:
|
|
47
|
+
ttl (int): time to live (in seconds) for record in cache
|
|
48
48
|
"""
|
|
49
49
|
self.ttl = ttl
|
|
50
50
|
self.handlers = {}
|
|
@@ -56,50 +56,46 @@ class HandlersCache:
|
|
|
56
56
|
self._stop_clean()
|
|
57
57
|
|
|
58
58
|
def _start_clean(self) -> None:
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
isinstance(self.cleaner_thread, threading.Thread)
|
|
63
|
-
and self.cleaner_thread.is_alive()
|
|
64
|
-
):
|
|
59
|
+
"""start worker that close connections after ttl expired"""
|
|
60
|
+
if isinstance(self.cleaner_thread, threading.Thread) and self.cleaner_thread.is_alive():
|
|
65
61
|
return
|
|
66
62
|
self._stop_event.clear()
|
|
67
|
-
self.cleaner_thread = threading.Thread(target=self._clean, name=
|
|
63
|
+
self.cleaner_thread = threading.Thread(target=self._clean, name="HandlersCache.clean")
|
|
68
64
|
self.cleaner_thread.daemon = True
|
|
69
65
|
self.cleaner_thread.start()
|
|
70
66
|
|
|
71
67
|
def _stop_clean(self) -> None:
|
|
72
|
-
"""
|
|
73
|
-
"""
|
|
68
|
+
"""stop clean worker"""
|
|
74
69
|
self._stop_event.set()
|
|
75
70
|
|
|
76
71
|
def set(self, handler: DatabaseHandler):
|
|
77
|
-
"""
|
|
72
|
+
"""add (or replace) handler in cache
|
|
78
73
|
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
Args:
|
|
75
|
+
handler (DatabaseHandler)
|
|
81
76
|
"""
|
|
82
77
|
with self._lock:
|
|
83
78
|
try:
|
|
84
79
|
# If the handler is defined to be thread safe, set 0 as the last element of the key, otherwise set the thrad ID.
|
|
85
|
-
key = (
|
|
80
|
+
key = (
|
|
81
|
+
handler.name,
|
|
82
|
+
ctx.company_id,
|
|
83
|
+
0 if getattr(handler, "thread_safe", False) else threading.get_native_id(),
|
|
84
|
+
)
|
|
86
85
|
handler.connect()
|
|
87
|
-
self.handlers[key] = {
|
|
88
|
-
'handler': handler,
|
|
89
|
-
'expired_at': time.time() + self.ttl
|
|
90
|
-
}
|
|
86
|
+
self.handlers[key] = {"handler": handler, "expired_at": time.time() + self.ttl}
|
|
91
87
|
except Exception:
|
|
92
88
|
pass
|
|
93
89
|
self._start_clean()
|
|
94
90
|
|
|
95
91
|
def get(self, name: str) -> Optional[DatabaseHandler]:
|
|
96
|
-
"""
|
|
92
|
+
"""get handler from cache by name
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
Args:
|
|
95
|
+
name (str): handler name
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
Returns:
|
|
98
|
+
DatabaseHandler
|
|
103
99
|
"""
|
|
104
100
|
with self._lock:
|
|
105
101
|
# If the handler is not thread safe, the thread ID will be assigned to the last element of the key.
|
|
@@ -107,19 +103,16 @@ class HandlersCache:
|
|
|
107
103
|
if key not in self.handlers:
|
|
108
104
|
# If the handler is thread safe, a 0 will be assigned to the last element of the key.
|
|
109
105
|
key = (name, ctx.company_id, 0)
|
|
110
|
-
if (
|
|
111
|
-
key not in self.handlers
|
|
112
|
-
or self.handlers[key]['expired_at'] < time.time()
|
|
113
|
-
):
|
|
106
|
+
if key not in self.handlers or self.handlers[key]["expired_at"] < time.time():
|
|
114
107
|
return None
|
|
115
|
-
self.handlers[key][
|
|
116
|
-
return self.handlers[key][
|
|
108
|
+
self.handlers[key]["expired_at"] = time.time() + self.ttl
|
|
109
|
+
return self.handlers[key]["handler"]
|
|
117
110
|
|
|
118
111
|
def delete(self, name: str) -> None:
|
|
119
|
-
"""
|
|
112
|
+
"""delete handler from cache
|
|
120
113
|
|
|
121
|
-
|
|
122
|
-
|
|
114
|
+
Args:
|
|
115
|
+
name (str): handler name
|
|
123
116
|
"""
|
|
124
117
|
with self._lock:
|
|
125
118
|
key = (name, ctx.company_id, threading.get_native_id())
|
|
@@ -133,14 +126,13 @@ class HandlersCache:
|
|
|
133
126
|
self._stop_clean()
|
|
134
127
|
|
|
135
128
|
def _clean(self) -> None:
|
|
136
|
-
"""
|
|
137
|
-
"""
|
|
129
|
+
"""worker that delete from cache handlers that was not in use for ttl"""
|
|
138
130
|
while self._stop_event.wait(timeout=3) is False:
|
|
139
131
|
with self._lock:
|
|
140
132
|
for key in list(self.handlers.keys()):
|
|
141
133
|
if (
|
|
142
|
-
self.handlers[key][
|
|
143
|
-
and sys.getrefcount(self.handlers[key]) == 2
|
|
134
|
+
self.handlers[key]["expired_at"] < time.time()
|
|
135
|
+
and sys.getrefcount(self.handlers[key]) == 2 # returned ref count is always 1 higher
|
|
144
136
|
):
|
|
145
137
|
try:
|
|
146
138
|
self.handlers[key].disconnect()
|
|
@@ -163,50 +155,43 @@ class IntegrationController:
|
|
|
163
155
|
|
|
164
156
|
def _add_integration_record(self, name, engine, connection_args):
|
|
165
157
|
integration_record = db.Integration(
|
|
166
|
-
name=name,
|
|
167
|
-
engine=engine,
|
|
168
|
-
data=connection_args or {},
|
|
169
|
-
company_id=ctx.company_id
|
|
158
|
+
name=name, engine=engine, data=connection_args or {}, company_id=ctx.company_id
|
|
170
159
|
)
|
|
171
160
|
db.session.add(integration_record)
|
|
172
161
|
db.session.commit()
|
|
173
162
|
return integration_record.id
|
|
174
163
|
|
|
175
164
|
def add(self, name, engine, connection_args):
|
|
176
|
-
|
|
177
165
|
logger.debug(
|
|
178
166
|
"%s: add method calling name=%s, engine=%s, connection_args=%s, company_id=%s",
|
|
179
|
-
self.__class__.__name__,
|
|
167
|
+
self.__class__.__name__,
|
|
168
|
+
name,
|
|
169
|
+
engine,
|
|
170
|
+
connection_args,
|
|
171
|
+
ctx.company_id,
|
|
180
172
|
)
|
|
181
173
|
handler_meta = self.get_handler_meta(engine)
|
|
182
174
|
|
|
183
|
-
accept_connection_args = handler_meta.get(
|
|
175
|
+
accept_connection_args = handler_meta.get("connection_args")
|
|
184
176
|
logger.debug("%s: accept_connection_args - %s", self.__class__.__name__, accept_connection_args)
|
|
185
177
|
|
|
186
178
|
files_dir = None
|
|
187
179
|
if accept_connection_args is not None and connection_args is not None:
|
|
188
180
|
for arg_name, arg_value in connection_args.items():
|
|
189
|
-
if
|
|
190
|
-
arg_name in accept_connection_args
|
|
191
|
-
and accept_connection_args[arg_name]['type'] == ARG_TYPE.PATH
|
|
192
|
-
):
|
|
181
|
+
if arg_name in accept_connection_args and accept_connection_args[arg_name]["type"] == ARG_TYPE.PATH:
|
|
193
182
|
if files_dir is None:
|
|
194
|
-
files_dir = tempfile.mkdtemp(prefix=
|
|
183
|
+
files_dir = tempfile.mkdtemp(prefix="mindsdb_files_")
|
|
195
184
|
shutil.copy(arg_value, files_dir)
|
|
196
185
|
connection_args[arg_name] = Path(arg_value).name
|
|
197
186
|
|
|
198
187
|
integration_id = self._add_integration_record(name, engine, connection_args)
|
|
199
188
|
|
|
200
189
|
if files_dir is not None:
|
|
201
|
-
store = FileStorage(
|
|
202
|
-
|
|
203
|
-
resource_id=integration_id,
|
|
204
|
-
sync=False
|
|
205
|
-
)
|
|
206
|
-
store.add(files_dir, '')
|
|
190
|
+
store = FileStorage(resource_group=RESOURCE_GROUP.INTEGRATION, resource_id=integration_id, sync=False)
|
|
191
|
+
store.add(files_dir, "")
|
|
207
192
|
store.push()
|
|
208
193
|
|
|
209
|
-
if handler_meta.get(
|
|
194
|
+
if handler_meta.get("type") == HANDLER_TYPE.ML:
|
|
210
195
|
ml_handler = self.get_ml_handler(name)
|
|
211
196
|
ml_handler.create_engine(connection_args, integration_id)
|
|
212
197
|
|
|
@@ -215,7 +200,7 @@ class IntegrationController:
|
|
|
215
200
|
def modify(self, name, data):
|
|
216
201
|
self.handlers_cache.delete(name)
|
|
217
202
|
integration_record = self._get_integration_record(name)
|
|
218
|
-
if isinstance(integration_record.data, dict) and integration_record.data.get(
|
|
203
|
+
if isinstance(integration_record.data, dict) and integration_record.data.get("is_demo") is True:
|
|
219
204
|
raise ValueError("It is forbidden to change properties of the demo object")
|
|
220
205
|
old_data = deepcopy(integration_record.data)
|
|
221
206
|
for k in old_data:
|
|
@@ -226,8 +211,8 @@ class IntegrationController:
|
|
|
226
211
|
db.session.commit()
|
|
227
212
|
|
|
228
213
|
def delete(self, name):
|
|
229
|
-
if name in (
|
|
230
|
-
raise Exception(
|
|
214
|
+
if name in ("files", "lightwood"):
|
|
215
|
+
raise Exception("Unable to drop: is system database")
|
|
231
216
|
|
|
232
217
|
self.handlers_cache.delete(name)
|
|
233
218
|
|
|
@@ -235,32 +220,32 @@ class IntegrationController:
|
|
|
235
220
|
if name in self.handler_modules:
|
|
236
221
|
handler = self.handler_modules[name]
|
|
237
222
|
|
|
238
|
-
if getattr(handler,
|
|
239
|
-
raise Exception(
|
|
223
|
+
if getattr(handler, "permanent", False) is True:
|
|
224
|
+
raise Exception("Unable to drop permanent integration")
|
|
240
225
|
|
|
241
226
|
integration_record = self._get_integration_record(name)
|
|
242
|
-
if isinstance(integration_record.data, dict) and integration_record.data.get(
|
|
243
|
-
raise Exception(
|
|
227
|
+
if isinstance(integration_record.data, dict) and integration_record.data.get("is_demo") is True:
|
|
228
|
+
raise Exception("Unable to drop demo object")
|
|
244
229
|
|
|
245
230
|
# if this is ml engine
|
|
246
231
|
engine_models = get_model_records(ml_handler_name=name, deleted_at=None)
|
|
247
232
|
active_models = [m.name for m in engine_models if m.deleted_at is None]
|
|
248
233
|
if len(active_models) > 0:
|
|
249
|
-
raise Exception(f
|
|
234
|
+
raise Exception(f"Unable to drop ml engine with active models: {active_models}")
|
|
250
235
|
|
|
251
236
|
# check linked KBs
|
|
252
237
|
kb = db.KnowledgeBase.query.filter_by(vector_database_id=integration_record.id).first()
|
|
253
238
|
if kb is not None:
|
|
254
|
-
raise Exception(f
|
|
239
|
+
raise Exception(f"Unable to drop, integration is used by knowledge base: {kb.name}")
|
|
255
240
|
|
|
256
241
|
# check linked predictors
|
|
257
242
|
models = get_model_records()
|
|
258
243
|
for model in models:
|
|
259
244
|
if (
|
|
260
245
|
model.data_integration_ref is not None
|
|
261
|
-
and model.data_integration_ref.get(
|
|
262
|
-
and isinstance(model.data_integration_ref.get(
|
|
263
|
-
and model.data_integration_ref[
|
|
246
|
+
and model.data_integration_ref.get("type") == "integration"
|
|
247
|
+
and isinstance(model.data_integration_ref.get("id"), int)
|
|
248
|
+
and model.data_integration_ref["id"] == integration_record.id
|
|
264
249
|
):
|
|
265
250
|
model.data_integration_ref = None
|
|
266
251
|
|
|
@@ -269,6 +254,14 @@ class IntegrationController:
|
|
|
269
254
|
if model.deleted_at is not None:
|
|
270
255
|
model.integration_id = None
|
|
271
256
|
|
|
257
|
+
# Remove the integration metadata from the data catalog (if enabled).
|
|
258
|
+
# TODO: Can this be handled via cascading delete in the database?
|
|
259
|
+
if self.get_handler_meta(integration_record.engine).get("type") == HANDLER_TYPE.DATA and Config().get(
|
|
260
|
+
"data_catalog", {}
|
|
261
|
+
).get("enabled", False):
|
|
262
|
+
data_catalog_reader = DataCatalogLoader(database_name=name)
|
|
263
|
+
data_catalog_reader.unload_metadata()
|
|
264
|
+
|
|
272
265
|
db.session.delete(integration_record)
|
|
273
266
|
db.session.commit()
|
|
274
267
|
|
|
@@ -281,82 +274,77 @@ class IntegrationController:
|
|
|
281
274
|
return None
|
|
282
275
|
data = deepcopy(integration_record.data)
|
|
283
276
|
|
|
284
|
-
bundle_path = data.get(
|
|
285
|
-
mysql_ssl_ca = data.get(
|
|
286
|
-
mysql_ssl_cert = data.get(
|
|
287
|
-
mysql_ssl_key = data.get(
|
|
277
|
+
bundle_path = data.get("secure_connect_bundle")
|
|
278
|
+
mysql_ssl_ca = data.get("ssl_ca")
|
|
279
|
+
mysql_ssl_cert = data.get("ssl_cert")
|
|
280
|
+
mysql_ssl_key = data.get("ssl_key")
|
|
288
281
|
if (
|
|
289
|
-
data.get(
|
|
282
|
+
data.get("type") in ("mysql", "mariadb")
|
|
290
283
|
and (
|
|
291
284
|
self._is_not_empty_str(mysql_ssl_ca)
|
|
292
285
|
or self._is_not_empty_str(mysql_ssl_cert)
|
|
293
286
|
or self._is_not_empty_str(mysql_ssl_key)
|
|
294
287
|
)
|
|
295
|
-
or data.get(
|
|
288
|
+
or data.get("type") in ("cassandra", "scylla")
|
|
296
289
|
and bundle_path is not None
|
|
297
290
|
):
|
|
298
291
|
fs_store = FsStore()
|
|
299
|
-
integrations_dir = Config()[
|
|
300
|
-
folder_name = f
|
|
301
|
-
fs_store.get(
|
|
302
|
-
folder_name,
|
|
303
|
-
base_dir=integrations_dir
|
|
304
|
-
)
|
|
292
|
+
integrations_dir = Config()["paths"]["integrations"]
|
|
293
|
+
folder_name = f"integration_files_{integration_record.company_id}_{integration_record.id}"
|
|
294
|
+
fs_store.get(folder_name, base_dir=integrations_dir)
|
|
305
295
|
|
|
306
296
|
handler_meta = self.get_handler_metadata(integration_record.engine)
|
|
307
297
|
integration_type = None
|
|
308
298
|
if isinstance(handler_meta, dict):
|
|
309
299
|
# in other cases, the handler directory is likely not exist.
|
|
310
|
-
integration_type = handler_meta.get(
|
|
300
|
+
integration_type = handler_meta.get("type")
|
|
311
301
|
|
|
312
302
|
if show_secrets is False and handler_meta is not None:
|
|
313
|
-
connection_args = handler_meta.get(
|
|
303
|
+
connection_args = handler_meta.get("connection_args", None)
|
|
314
304
|
if isinstance(connection_args, dict):
|
|
315
305
|
if integration_type == HANDLER_TYPE.DATA:
|
|
316
306
|
for key, value in connection_args.items():
|
|
317
|
-
if key in data and value.get(
|
|
318
|
-
data[key] =
|
|
307
|
+
if key in data and value.get("secret", False) is True:
|
|
308
|
+
data[key] = "******"
|
|
319
309
|
elif integration_type == HANDLER_TYPE.ML:
|
|
320
|
-
creation_args = connection_args.get(
|
|
310
|
+
creation_args = connection_args.get("creation_args")
|
|
321
311
|
if isinstance(creation_args, dict):
|
|
322
312
|
for key, value in creation_args.items():
|
|
323
|
-
if key in data and value.get(
|
|
324
|
-
data[key] =
|
|
313
|
+
if key in data and value.get("secret", False) is True:
|
|
314
|
+
data[key] = "******"
|
|
325
315
|
else:
|
|
326
|
-
raise ValueError(f
|
|
316
|
+
raise ValueError(f"Unexpected handler type: {integration_type}")
|
|
327
317
|
else:
|
|
328
318
|
# region obsolete, del in future
|
|
329
|
-
if
|
|
330
|
-
data[
|
|
319
|
+
if "password" in data:
|
|
320
|
+
data["password"] = None
|
|
331
321
|
if (
|
|
332
|
-
data.get(
|
|
333
|
-
and isinstance(data.get(
|
|
334
|
-
and
|
|
322
|
+
data.get("type") == "redis"
|
|
323
|
+
and isinstance(data.get("connection"), dict)
|
|
324
|
+
and "password" in data["connection"]
|
|
335
325
|
):
|
|
336
|
-
data[
|
|
326
|
+
data["connection"] = None
|
|
337
327
|
# endregion
|
|
338
328
|
|
|
339
329
|
class_type, permanent = None, False
|
|
340
330
|
if handler_meta is not None:
|
|
341
|
-
class_type = handler_meta.get(
|
|
342
|
-
permanent = handler_meta.get(
|
|
331
|
+
class_type = handler_meta.get("class_type")
|
|
332
|
+
permanent = handler_meta.get("permanent", False)
|
|
343
333
|
|
|
344
334
|
return {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
335
|
+
"id": integration_record.id,
|
|
336
|
+
"name": integration_record.name,
|
|
337
|
+
"type": integration_type,
|
|
338
|
+
"class_type": class_type,
|
|
339
|
+
"engine": integration_record.engine,
|
|
340
|
+
"permanent": permanent,
|
|
341
|
+
"date_last_update": deepcopy(integration_record.updated_at), # to del ?
|
|
342
|
+
"connection_data": data,
|
|
353
343
|
}
|
|
354
344
|
|
|
355
345
|
def get_by_id(self, integration_id, show_secrets=True):
|
|
356
346
|
integration_record = (
|
|
357
|
-
db.session.query(db.Integration)
|
|
358
|
-
.filter_by(company_id=ctx.company_id, id=integration_id)
|
|
359
|
-
.first()
|
|
347
|
+
db.session.query(db.Integration).filter_by(company_id=ctx.company_id, id=integration_id).first()
|
|
360
348
|
)
|
|
361
349
|
return self._get_integration_record_data(integration_record, show_secrets)
|
|
362
350
|
|
|
@@ -379,20 +367,21 @@ class IntegrationController:
|
|
|
379
367
|
db.Integration
|
|
380
368
|
"""
|
|
381
369
|
if case_sensitive:
|
|
382
|
-
integration_records = db.session.query(db.Integration).filter_by(
|
|
383
|
-
company_id=ctx.company_id,
|
|
384
|
-
name=name
|
|
385
|
-
).all()
|
|
370
|
+
integration_records = db.session.query(db.Integration).filter_by(company_id=ctx.company_id, name=name).all()
|
|
386
371
|
if len(integration_records) > 1:
|
|
387
372
|
raise Exception(f"There is {len(integration_records)} integrations with name '{name}'")
|
|
388
373
|
if len(integration_records) == 0:
|
|
389
374
|
raise EntityNotExistsError(f"There is no integration with name '{name}'")
|
|
390
375
|
integration_record = integration_records[0]
|
|
391
376
|
else:
|
|
392
|
-
integration_record =
|
|
393
|
-
(db.Integration
|
|
394
|
-
|
|
395
|
-
|
|
377
|
+
integration_record = (
|
|
378
|
+
db.session.query(db.Integration)
|
|
379
|
+
.filter(
|
|
380
|
+
(db.Integration.company_id == ctx.company_id)
|
|
381
|
+
& (func.lower(db.Integration.name) == func.lower(name))
|
|
382
|
+
)
|
|
383
|
+
.first()
|
|
384
|
+
)
|
|
396
385
|
if integration_record is None:
|
|
397
386
|
raise EntityNotExistsError(f"There is no integration with name '{name}'")
|
|
398
387
|
|
|
@@ -407,21 +396,28 @@ class IntegrationController:
|
|
|
407
396
|
integration_dict[record.name] = self._get_integration_record_data(record, show_secrets)
|
|
408
397
|
return integration_dict
|
|
409
398
|
|
|
410
|
-
def _make_handler_args(
|
|
411
|
-
|
|
399
|
+
def _make_handler_args(
|
|
400
|
+
self,
|
|
401
|
+
name: str,
|
|
402
|
+
handler_type: str,
|
|
403
|
+
connection_data: dict,
|
|
404
|
+
integration_id: int = None,
|
|
405
|
+
file_storage: FileStorage = None,
|
|
406
|
+
handler_storage: HandlerStorage = None,
|
|
407
|
+
):
|
|
412
408
|
handler_args = dict(
|
|
413
409
|
name=name,
|
|
414
410
|
integration_id=integration_id,
|
|
415
411
|
connection_data=connection_data,
|
|
416
412
|
file_storage=file_storage,
|
|
417
|
-
handler_storage=handler_storage
|
|
413
|
+
handler_storage=handler_storage,
|
|
418
414
|
)
|
|
419
415
|
|
|
420
|
-
if handler_type ==
|
|
421
|
-
handler_args[
|
|
416
|
+
if handler_type == "files":
|
|
417
|
+
handler_args["file_controller"] = FileController()
|
|
422
418
|
elif self.handler_modules.get(handler_type, False).type == HANDLER_TYPE.ML:
|
|
423
|
-
handler_args[
|
|
424
|
-
handler_args[
|
|
419
|
+
handler_args["handler_controller"] = self
|
|
420
|
+
handler_args["company_id"] = ctx.company_id
|
|
425
421
|
|
|
426
422
|
return handler_args
|
|
427
423
|
|
|
@@ -439,12 +435,9 @@ class IntegrationController:
|
|
|
439
435
|
integration_id = int(time.time() * 10000)
|
|
440
436
|
|
|
441
437
|
file_storage = FileStorage(
|
|
442
|
-
resource_group=RESOURCE_GROUP.INTEGRATION,
|
|
443
|
-
resource_id=integration_id,
|
|
444
|
-
root_dir='tmp',
|
|
445
|
-
sync=False
|
|
438
|
+
resource_group=RESOURCE_GROUP.INTEGRATION, resource_id=integration_id, root_dir="tmp", sync=False
|
|
446
439
|
)
|
|
447
|
-
handler_storage = HandlerStorage(integration_id, root_dir=
|
|
440
|
+
handler_storage = HandlerStorage(integration_id, root_dir="tmp", is_temporal=True)
|
|
448
441
|
|
|
449
442
|
handler_meta = self.get_handler_meta(engine)
|
|
450
443
|
if handler_meta is None:
|
|
@@ -466,7 +459,7 @@ class IntegrationController:
|
|
|
466
459
|
|
|
467
460
|
def copy_integration_storage(self, integration_id_from, integration_id_to):
|
|
468
461
|
storage_from = HandlerStorage(integration_id_from)
|
|
469
|
-
root_path =
|
|
462
|
+
root_path = ""
|
|
470
463
|
|
|
471
464
|
if storage_from.is_empty():
|
|
472
465
|
return None
|
|
@@ -494,7 +487,7 @@ class IntegrationController:
|
|
|
494
487
|
if integration_meta is None:
|
|
495
488
|
raise Exception(f"Handler '{name}' does not exists")
|
|
496
489
|
|
|
497
|
-
if integration_meta.get(
|
|
490
|
+
if integration_meta.get("type") != HANDLER_TYPE.ML:
|
|
498
491
|
raise Exception(f"Handler '{name}' must be ML type")
|
|
499
492
|
|
|
500
493
|
logger.info(
|
|
@@ -504,7 +497,7 @@ class IntegrationController:
|
|
|
504
497
|
handler = BaseMLEngineExec(
|
|
505
498
|
name=integration_record.name,
|
|
506
499
|
integration_id=integration_record.id,
|
|
507
|
-
handler_module=self.handler_modules[integration_engine]
|
|
500
|
+
handler_module=self.handler_modules[integration_engine],
|
|
508
501
|
)
|
|
509
502
|
|
|
510
503
|
return handler
|
|
@@ -532,35 +525,36 @@ class IntegrationController:
|
|
|
532
525
|
if integration_meta is None:
|
|
533
526
|
raise Exception(f"Handler '{name}' does not exist")
|
|
534
527
|
|
|
535
|
-
if integration_meta.get(
|
|
528
|
+
if integration_meta.get("type") != HANDLER_TYPE.DATA:
|
|
536
529
|
raise Exception(f"Handler '{name}' must be DATA type")
|
|
537
530
|
|
|
538
531
|
integration_data = self._get_integration_record_data(integration_record, True)
|
|
539
532
|
if integration_data is None:
|
|
540
533
|
raise Exception(f"Can't find integration_record for handler '{name}'")
|
|
541
|
-
connection_data = integration_data.get(
|
|
534
|
+
connection_data = integration_data.get("connection_data", {})
|
|
542
535
|
logger.debug(
|
|
543
536
|
"%s.get_handler: connection_data=%s, engine=%s",
|
|
544
537
|
self.__class__.__name__,
|
|
545
|
-
connection_data,
|
|
538
|
+
connection_data,
|
|
539
|
+
integration_engine,
|
|
546
540
|
)
|
|
547
541
|
|
|
548
542
|
if integration_meta["import"]["success"] is False:
|
|
549
|
-
msg = dedent(f
|
|
543
|
+
msg = dedent(f"""\
|
|
550
544
|
Handler '{integration_engine}' cannot be used. Reason is:
|
|
551
|
-
{integration_meta[
|
|
552
|
-
|
|
553
|
-
is_cloud = Config().get(
|
|
545
|
+
{integration_meta["import"]["error_message"]}
|
|
546
|
+
""")
|
|
547
|
+
is_cloud = Config().get("cloud", False)
|
|
554
548
|
if is_cloud is False:
|
|
555
|
-
msg += dedent(f
|
|
549
|
+
msg += dedent(f"""
|
|
556
550
|
|
|
557
551
|
If error is related to missing dependencies, then try to run command in shell and restart mindsdb:
|
|
558
552
|
pip install mindsdb[{integration_engine}]
|
|
559
|
-
|
|
553
|
+
""")
|
|
560
554
|
logger.debug(msg)
|
|
561
555
|
raise Exception(msg)
|
|
562
556
|
|
|
563
|
-
connection_args = integration_meta.get(
|
|
557
|
+
connection_args = integration_meta.get("connection_args")
|
|
564
558
|
logger.debug("%s.get_handler: connection args - %s", self.__class__.__name__, connection_args)
|
|
565
559
|
|
|
566
560
|
file_storage = FileStorage(
|
|
@@ -572,11 +566,11 @@ class IntegrationController:
|
|
|
572
566
|
|
|
573
567
|
if isinstance(connection_args, (dict, OrderedDict)):
|
|
574
568
|
files_to_get = {
|
|
575
|
-
arg_name: arg_value
|
|
576
|
-
|
|
569
|
+
arg_name: arg_value
|
|
570
|
+
for arg_name, arg_value in connection_data.items()
|
|
571
|
+
if arg_name in connection_args and connection_args.get(arg_name)["type"] == ARG_TYPE.PATH
|
|
577
572
|
}
|
|
578
573
|
if len(files_to_get) > 0:
|
|
579
|
-
|
|
580
574
|
for file_name, file_path in files_to_get.items():
|
|
581
575
|
connection_data[file_name] = file_storage.get_path(file_path)
|
|
582
576
|
|
|
@@ -584,9 +578,9 @@ class IntegrationController:
|
|
|
584
578
|
name=name,
|
|
585
579
|
handler_type=integration_engine,
|
|
586
580
|
connection_data=connection_data,
|
|
587
|
-
integration_id=integration_data[
|
|
581
|
+
integration_id=integration_data["id"],
|
|
588
582
|
file_storage=file_storage,
|
|
589
|
-
handler_storage=handler_storage
|
|
583
|
+
handler_storage=handler_storage,
|
|
590
584
|
)
|
|
591
585
|
|
|
592
586
|
HandlerClass = self.handler_modules[integration_engine].Handler
|
|
@@ -602,82 +596,76 @@ class IntegrationController:
|
|
|
602
596
|
handler_meta = self._get_handler_meta(handler_name)
|
|
603
597
|
except Exception as e:
|
|
604
598
|
handler_meta = self.handlers_import_status[handler_name]
|
|
605
|
-
handler_meta[
|
|
606
|
-
handler_meta[
|
|
599
|
+
handler_meta["import"]["success"] = False
|
|
600
|
+
handler_meta["import"]["error_message"] = str(e)
|
|
607
601
|
|
|
608
602
|
self.handlers_import_status[handler_name] = handler_meta
|
|
609
603
|
|
|
610
604
|
def _read_dependencies(self, path):
|
|
611
605
|
dependencies = []
|
|
612
|
-
requirements_txt = Path(path).joinpath(
|
|
606
|
+
requirements_txt = Path(path).joinpath("requirements.txt")
|
|
613
607
|
if requirements_txt.is_file():
|
|
614
|
-
with open(str(requirements_txt),
|
|
615
|
-
dependencies = [x.strip(
|
|
608
|
+
with open(str(requirements_txt), "rt") as f:
|
|
609
|
+
dependencies = [x.strip(" \t\n") for x in f.readlines()]
|
|
616
610
|
dependencies = [x for x in dependencies if len(x) > 0]
|
|
617
611
|
return dependencies
|
|
618
612
|
|
|
619
613
|
def _get_handler_meta(self, handler_name):
|
|
620
|
-
|
|
621
614
|
module = self.handler_modules[handler_name]
|
|
622
615
|
|
|
623
616
|
handler_dir = Path(module.__path__[0])
|
|
624
617
|
handler_folder_name = handler_dir.name
|
|
625
618
|
|
|
626
|
-
import_error = getattr(module,
|
|
619
|
+
import_error = getattr(module, "import_error", None)
|
|
627
620
|
handler_meta = self.handlers_import_status[handler_name]
|
|
628
|
-
handler_meta[
|
|
629
|
-
handler_meta[
|
|
630
|
-
handler_meta[
|
|
621
|
+
handler_meta["import"]["success"] = import_error is None
|
|
622
|
+
handler_meta["version"] = module.version
|
|
623
|
+
handler_meta["thread_safe"] = getattr(module, "thread_safe", False)
|
|
631
624
|
|
|
632
625
|
if import_error is not None:
|
|
633
|
-
handler_meta[
|
|
626
|
+
handler_meta["import"]["error_message"] = str(import_error)
|
|
634
627
|
|
|
635
|
-
handler_type = getattr(module,
|
|
628
|
+
handler_type = getattr(module, "type", None)
|
|
636
629
|
handler_class = None
|
|
637
|
-
if hasattr(module,
|
|
630
|
+
if hasattr(module, "Handler") and inspect.isclass(module.Handler):
|
|
638
631
|
handler_class = module.Handler
|
|
639
632
|
if issubclass(handler_class, BaseMLEngine):
|
|
640
|
-
handler_meta[
|
|
633
|
+
handler_meta["class_type"] = "ml"
|
|
641
634
|
elif issubclass(handler_class, DatabaseHandler):
|
|
642
|
-
handler_meta[
|
|
635
|
+
handler_meta["class_type"] = "sql"
|
|
643
636
|
if issubclass(handler_class, APIHandler):
|
|
644
|
-
handler_meta[
|
|
637
|
+
handler_meta["class_type"] = "api"
|
|
645
638
|
|
|
646
639
|
if handler_type == HANDLER_TYPE.ML:
|
|
647
640
|
# for ml engines, patch the connection_args from the argument probing
|
|
648
641
|
if handler_class:
|
|
649
642
|
try:
|
|
650
643
|
prediction_args = handler_class.prediction_args()
|
|
651
|
-
creation_args = getattr(module,
|
|
652
|
-
connection_args = {
|
|
653
|
-
|
|
654
|
-
"creation_args": creation_args
|
|
655
|
-
}
|
|
656
|
-
setattr(module, 'connection_args', connection_args)
|
|
644
|
+
creation_args = getattr(module, "creation_args", handler_class.creation_args())
|
|
645
|
+
connection_args = {"prediction": prediction_args, "creation_args": creation_args}
|
|
646
|
+
setattr(module, "connection_args", connection_args)
|
|
657
647
|
logger.debug("Patched connection_args for %s", handler_folder_name)
|
|
658
648
|
except Exception as e:
|
|
659
649
|
# do nothing
|
|
660
650
|
logger.debug("Failed to patch connection_args for %s, reason: %s", handler_folder_name, str(e))
|
|
661
651
|
|
|
662
|
-
module_attrs = [
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
'title'
|
|
668
|
-
] if hasattr(module, attr)]
|
|
652
|
+
module_attrs = [
|
|
653
|
+
attr
|
|
654
|
+
for attr in ["connection_args_example", "connection_args", "description", "type", "title"]
|
|
655
|
+
if hasattr(module, attr)
|
|
656
|
+
]
|
|
669
657
|
|
|
670
658
|
for attr in module_attrs:
|
|
671
659
|
handler_meta[attr] = getattr(module, attr)
|
|
672
660
|
|
|
673
661
|
# endregion
|
|
674
|
-
if hasattr(module,
|
|
675
|
-
handler_meta[
|
|
662
|
+
if hasattr(module, "permanent"):
|
|
663
|
+
handler_meta["permanent"] = module.permanent
|
|
676
664
|
else:
|
|
677
|
-
if handler_meta.get(
|
|
678
|
-
handler_meta[
|
|
665
|
+
if handler_meta.get("name") in ("files", "views", "lightwood"):
|
|
666
|
+
handler_meta["permanent"] = True
|
|
679
667
|
else:
|
|
680
|
-
handler_meta[
|
|
668
|
+
handler_meta["permanent"] = False
|
|
681
669
|
|
|
682
670
|
return handler_meta
|
|
683
671
|
|
|
@@ -685,60 +673,60 @@ class IntegrationController:
|
|
|
685
673
|
icon = {}
|
|
686
674
|
try:
|
|
687
675
|
icon_path = handler_dir.joinpath(icon_path)
|
|
688
|
-
icon_type = icon_path.name[icon_path.name.rfind(
|
|
676
|
+
icon_type = icon_path.name[icon_path.name.rfind(".") + 1 :].lower()
|
|
689
677
|
|
|
690
|
-
if icon_type ==
|
|
691
|
-
with open(str(icon_path),
|
|
692
|
-
icon[
|
|
678
|
+
if icon_type == "svg":
|
|
679
|
+
with open(str(icon_path), "rt") as f:
|
|
680
|
+
icon["data"] = f.read()
|
|
693
681
|
else:
|
|
694
|
-
with open(str(icon_path),
|
|
695
|
-
icon[
|
|
682
|
+
with open(str(icon_path), "rb") as f:
|
|
683
|
+
icon["data"] = base64.b64encode(f.read()).decode("utf-8")
|
|
696
684
|
|
|
697
|
-
icon[
|
|
698
|
-
icon[
|
|
685
|
+
icon["name"] = icon_path.name
|
|
686
|
+
icon["type"] = icon_type
|
|
699
687
|
|
|
700
688
|
except Exception as e:
|
|
701
|
-
logger.error(f
|
|
689
|
+
logger.error(f"Error reading icon for {handler_dir}, {e}!")
|
|
702
690
|
return icon
|
|
703
691
|
|
|
704
692
|
def _load_handler_modules(self):
|
|
705
|
-
mindsdb_path = Path(importlib.util.find_spec(
|
|
706
|
-
handlers_path = mindsdb_path.joinpath(
|
|
693
|
+
mindsdb_path = Path(importlib.util.find_spec("mindsdb").origin).parent
|
|
694
|
+
handlers_path = mindsdb_path.joinpath("integrations/handlers")
|
|
707
695
|
|
|
708
696
|
# edge case: running from tests directory, find_spec finds the base folder instead of actual package
|
|
709
697
|
if not os.path.isdir(handlers_path):
|
|
710
|
-
mindsdb_path = Path(importlib.util.find_spec(
|
|
711
|
-
handlers_path = mindsdb_path.joinpath(
|
|
698
|
+
mindsdb_path = Path(importlib.util.find_spec("mindsdb").origin).parent.joinpath("mindsdb")
|
|
699
|
+
handlers_path = mindsdb_path.joinpath("integrations/handlers")
|
|
712
700
|
|
|
713
701
|
self.handler_modules = {}
|
|
714
702
|
self.handlers_import_status = {}
|
|
715
703
|
for handler_dir in handlers_path.iterdir():
|
|
716
|
-
if handler_dir.is_dir() is False or handler_dir.name.startswith(
|
|
704
|
+
if handler_dir.is_dir() is False or handler_dir.name.startswith("__"):
|
|
717
705
|
continue
|
|
718
706
|
|
|
719
707
|
handler_info = self._get_handler_info(handler_dir)
|
|
720
|
-
if
|
|
708
|
+
if "name" not in handler_info:
|
|
721
709
|
continue
|
|
722
|
-
handler_name = handler_info[
|
|
710
|
+
handler_name = handler_info["name"]
|
|
723
711
|
dependencies = self._read_dependencies(handler_dir)
|
|
724
712
|
handler_meta = {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
713
|
+
"path": handler_dir,
|
|
714
|
+
"import": {
|
|
715
|
+
"success": None,
|
|
716
|
+
"error_message": None,
|
|
717
|
+
"folder": handler_dir.name,
|
|
718
|
+
"dependencies": dependencies,
|
|
731
719
|
},
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
720
|
+
"name": handler_name,
|
|
721
|
+
"permanent": handler_info.get("permanent", False),
|
|
722
|
+
"connection_args": handler_info.get("connection_args", None),
|
|
723
|
+
"class_type": handler_info.get("class_type", None),
|
|
724
|
+
"type": handler_info.get("type"),
|
|
737
725
|
}
|
|
738
|
-
if
|
|
739
|
-
icon = self._get_handler_icon(handler_dir, handler_info[
|
|
726
|
+
if "icon_path" in handler_info:
|
|
727
|
+
icon = self._get_handler_icon(handler_dir, handler_info["icon_path"])
|
|
740
728
|
if icon:
|
|
741
|
-
handler_meta[
|
|
729
|
+
handler_meta["icon"] = icon
|
|
742
730
|
self.handlers_import_status[handler_name] = handler_meta
|
|
743
731
|
|
|
744
732
|
def _get_connection_args(self, args_file: Path, param_name: str) -> dict:
|
|
@@ -758,7 +746,7 @@ class IntegrationController:
|
|
|
758
746
|
continue
|
|
759
747
|
if not item.targets[0].id == param_name:
|
|
760
748
|
continue
|
|
761
|
-
if hasattr(item.value,
|
|
749
|
+
if hasattr(item.value, "keywords"):
|
|
762
750
|
for keyword in item.value.keywords:
|
|
763
751
|
name = keyword.arg
|
|
764
752
|
params = keyword.value
|
|
@@ -802,7 +790,7 @@ class IntegrationController:
|
|
|
802
790
|
if module_file is None:
|
|
803
791
|
return
|
|
804
792
|
|
|
805
|
-
path = handler_dir / f
|
|
793
|
+
path = handler_dir / f"{module_file}.py"
|
|
806
794
|
|
|
807
795
|
if not path.exists():
|
|
808
796
|
return
|
|
@@ -812,9 +800,9 @@ class IntegrationController:
|
|
|
812
800
|
for item in code.body:
|
|
813
801
|
if isinstance(item, ast.ClassDef):
|
|
814
802
|
bases = [base.id for base in item.bases]
|
|
815
|
-
if
|
|
816
|
-
return
|
|
817
|
-
return
|
|
803
|
+
if "APIHandler" in bases or "MetaAPIHandler" in bases:
|
|
804
|
+
return "api"
|
|
805
|
+
return "sql"
|
|
818
806
|
|
|
819
807
|
def _get_handler_info(self, handler_dir: Path) -> dict:
|
|
820
808
|
"""
|
|
@@ -825,7 +813,7 @@ class IntegrationController:
|
|
|
825
813
|
- connection arguments
|
|
826
814
|
"""
|
|
827
815
|
|
|
828
|
-
init_file = handler_dir /
|
|
816
|
+
init_file = handler_dir / "__init__.py"
|
|
829
817
|
if not init_file.exists():
|
|
830
818
|
return {}
|
|
831
819
|
code = ast.parse(init_file.read_text())
|
|
@@ -838,47 +826,47 @@ class IntegrationController:
|
|
|
838
826
|
name = item.targets[0].id
|
|
839
827
|
if isinstance(item.value, ast.Constant):
|
|
840
828
|
info[name] = item.value.value
|
|
841
|
-
if isinstance(item.value, ast.Attribute) and name ==
|
|
842
|
-
if item.value.attr ==
|
|
829
|
+
if isinstance(item.value, ast.Attribute) and name == "type":
|
|
830
|
+
if item.value.attr == "ML":
|
|
843
831
|
info[name] = HANDLER_TYPE.ML
|
|
844
|
-
info[
|
|
832
|
+
info["class_type"] = "ml"
|
|
845
833
|
else:
|
|
846
834
|
info[name] = HANDLER_TYPE.DATA
|
|
847
|
-
info[
|
|
835
|
+
info["class_type"] = self._get_base_class_type(code, handler_dir) or "sql"
|
|
848
836
|
|
|
849
837
|
# connection args
|
|
850
|
-
if info[
|
|
851
|
-
args_file = handler_dir /
|
|
838
|
+
if info["type"] == HANDLER_TYPE.ML:
|
|
839
|
+
args_file = handler_dir / "creation_args.py"
|
|
852
840
|
if args_file.exists():
|
|
853
|
-
info[
|
|
841
|
+
info["connection_args"] = {
|
|
854
842
|
"prediction": {},
|
|
855
|
-
"creation_args": self._get_connection_args(args_file,
|
|
843
|
+
"creation_args": self._get_connection_args(args_file, "creation_args"),
|
|
856
844
|
}
|
|
857
845
|
else:
|
|
858
|
-
args_file = handler_dir /
|
|
846
|
+
args_file = handler_dir / "connection_args.py"
|
|
859
847
|
if args_file.exists():
|
|
860
|
-
info[
|
|
848
|
+
info["connection_args"] = self._get_connection_args(args_file, "connection_args")
|
|
861
849
|
|
|
862
850
|
return info
|
|
863
851
|
|
|
864
852
|
def import_handler(self, handler_name: str, base_import: str = None):
|
|
865
853
|
with self._import_lock:
|
|
866
854
|
handler_meta = self.handlers_import_status[handler_name]
|
|
867
|
-
handler_dir = handler_meta[
|
|
855
|
+
handler_dir = handler_meta["path"]
|
|
868
856
|
|
|
869
857
|
handler_folder_name = str(handler_dir.name)
|
|
870
858
|
if base_import is None:
|
|
871
|
-
base_import =
|
|
859
|
+
base_import = "mindsdb.integrations.handlers."
|
|
872
860
|
|
|
873
861
|
try:
|
|
874
|
-
handler_module = importlib.import_module(f
|
|
862
|
+
handler_module = importlib.import_module(f"{base_import}{handler_folder_name}")
|
|
875
863
|
self.handler_modules[handler_name] = handler_module
|
|
876
864
|
handler_meta = self._get_handler_meta(handler_name)
|
|
877
865
|
except Exception as e:
|
|
878
|
-
handler_meta[
|
|
879
|
-
handler_meta[
|
|
866
|
+
handler_meta["import"]["success"] = False
|
|
867
|
+
handler_meta["import"]["error_message"] = str(e)
|
|
880
868
|
|
|
881
|
-
self.handlers_import_status[handler_meta[
|
|
869
|
+
self.handlers_import_status[handler_meta["name"]] = handler_meta
|
|
882
870
|
return handler_meta
|
|
883
871
|
|
|
884
872
|
def get_handlers_import_status(self):
|