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
|
@@ -7,12 +7,22 @@ from pathlib import Path
|
|
|
7
7
|
import pandas
|
|
8
8
|
import pytest
|
|
9
9
|
from mindsdb_sql_parser.exceptions import ParsingException
|
|
10
|
-
from mindsdb_sql_parser.ast import
|
|
10
|
+
from mindsdb_sql_parser.ast import (
|
|
11
|
+
CreateTable,
|
|
12
|
+
DropTables,
|
|
13
|
+
Identifier,
|
|
14
|
+
Insert,
|
|
15
|
+
TableColumn,
|
|
16
|
+
Update,
|
|
17
|
+
)
|
|
11
18
|
|
|
12
19
|
from mindsdb.integrations.handlers.file_handler.file_handler import FileHandler
|
|
13
20
|
from mindsdb.integrations.libs.response import RESPONSE_TYPE
|
|
14
21
|
|
|
15
|
-
from mindsdb.integrations.utilities.files.file_reader import
|
|
22
|
+
from mindsdb.integrations.utilities.files.file_reader import (
|
|
23
|
+
FileReader,
|
|
24
|
+
FileProcessingError,
|
|
25
|
+
)
|
|
16
26
|
|
|
17
27
|
|
|
18
28
|
# Define a table to use as content for all of the file types
|
|
@@ -103,21 +113,18 @@ class TestIsItX:
|
|
|
103
113
|
|
|
104
114
|
def test_is_it_csv(self):
|
|
105
115
|
# We can't test xlsx or parquet here because they're binary files
|
|
106
|
-
for file_path, result in (
|
|
107
|
-
(csv_file(), True),
|
|
108
|
-
(json_file(), False)
|
|
109
|
-
):
|
|
116
|
+
for file_path, result in ((csv_file(), True), (json_file(), False)):
|
|
110
117
|
with open(file_path, "r") as fh:
|
|
111
118
|
assert FileReader.is_csv(StringIO(fh.read())) is result
|
|
112
119
|
|
|
113
120
|
def test_format(self):
|
|
114
121
|
for file_path, result in (
|
|
115
|
-
(csv_file(),
|
|
116
|
-
(xlsx_file(),
|
|
117
|
-
(json_file(),
|
|
118
|
-
(parquet_file(),
|
|
119
|
-
(txt_file(),
|
|
120
|
-
(pdf_file(),
|
|
122
|
+
(csv_file(), "csv"),
|
|
123
|
+
(xlsx_file(), "xlsx"),
|
|
124
|
+
(json_file(), "json"),
|
|
125
|
+
(parquet_file(), "parquet"),
|
|
126
|
+
(txt_file(), "txt"),
|
|
127
|
+
(pdf_file(), "pdf"),
|
|
121
128
|
):
|
|
122
129
|
assert FileReader(path=file_path).get_format() == result
|
|
123
130
|
|
|
@@ -182,6 +189,7 @@ class TestQuery:
|
|
|
182
189
|
|
|
183
190
|
def mock_get_file_path(self, name):
|
|
184
191
|
return csv_tmp
|
|
192
|
+
|
|
185
193
|
monkeypatch.setattr(MockFileController, "get_file_path", mock_get_file_path)
|
|
186
194
|
|
|
187
195
|
file_handler = FileHandler(file_controller=MockFileController())
|
|
@@ -255,14 +263,13 @@ class TestQuery:
|
|
|
255
263
|
|
|
256
264
|
|
|
257
265
|
def test_handle_source():
|
|
258
|
-
|
|
259
266
|
def get_reader(file_path):
|
|
260
267
|
# using path
|
|
261
268
|
reader = FileReader(path=file_path)
|
|
262
269
|
yield reader
|
|
263
270
|
|
|
264
271
|
# using file descriptor
|
|
265
|
-
with open(file_path,
|
|
272
|
+
with open(file_path, "rb") as fd:
|
|
266
273
|
reader = FileReader(file=fd)
|
|
267
274
|
yield reader
|
|
268
275
|
fd.seek(0)
|
|
@@ -310,14 +317,31 @@ def test_check_valid_dialects(csv_string, delimiter):
|
|
|
310
317
|
def test_tsv():
|
|
311
318
|
file = BytesIO(b"example;csv;file\tname")
|
|
312
319
|
|
|
313
|
-
reader = FileReader(file=file, name=
|
|
314
|
-
assert reader.get_format() ==
|
|
315
|
-
assert reader.parameters[
|
|
320
|
+
reader = FileReader(file=file, name="test.tsv")
|
|
321
|
+
assert reader.get_format() == "csv"
|
|
322
|
+
assert reader.parameters["delimiter"] == "\t"
|
|
316
323
|
|
|
317
324
|
df = reader.get_page_content()
|
|
318
325
|
assert len(df.columns) == 2
|
|
319
326
|
|
|
320
327
|
|
|
328
|
+
def test_bad_csv_header():
|
|
329
|
+
file = BytesIO(b" a,b ,c\n1,2,3\n")
|
|
330
|
+
reader = FileReader(file=file, name="test.tsv")
|
|
331
|
+
df = reader.get_page_content()
|
|
332
|
+
assert set(df.columns) == set(["a", "b", "c"])
|
|
333
|
+
|
|
334
|
+
wrong_data = [
|
|
335
|
+
b"a, ,c\n1,2,3\n",
|
|
336
|
+
b"a, \t,c\n1,2,3\n",
|
|
337
|
+
b" ,b,c\n1,2,3\n",
|
|
338
|
+
]
|
|
339
|
+
for data in wrong_data:
|
|
340
|
+
reader = FileReader(file=BytesIO(data), name="test.tsv")
|
|
341
|
+
with pytest.raises(FileProcessingError):
|
|
342
|
+
df = reader.get_page_content()
|
|
343
|
+
|
|
344
|
+
|
|
321
345
|
def test_check_invalid_dialects():
|
|
322
346
|
with pytest.raises(Exception):
|
|
323
347
|
FileHandler._get_csv_dialect("example csv file")
|
|
@@ -334,10 +358,7 @@ def test_get_tables():
|
|
|
334
358
|
assert response.type == RESPONSE_TYPE.TABLE
|
|
335
359
|
|
|
336
360
|
expected_df = pandas.DataFrame(
|
|
337
|
-
[
|
|
338
|
-
{"TABLE_NAME": x[0], "TABLE_ROWS": x[1], "TABLE_TYPE": "BASE TABLE"}
|
|
339
|
-
for x in file_records
|
|
340
|
-
]
|
|
361
|
+
[{"TABLE_NAME": x[0], "TABLE_ROWS": x[1], "TABLE_TYPE": "BASE TABLE"} for x in file_records]
|
|
341
362
|
)
|
|
342
363
|
|
|
343
364
|
assert response.data_frame.equals(expected_df)
|
|
@@ -349,8 +370,6 @@ def test_get_columns():
|
|
|
349
370
|
|
|
350
371
|
assert response.type == RESPONSE_TYPE.TABLE
|
|
351
372
|
|
|
352
|
-
expected_df = pandas.DataFrame(
|
|
353
|
-
[{"Field": x, "Type": "str"} for x in file_records[0][2]]
|
|
354
|
-
)
|
|
373
|
+
expected_df = pandas.DataFrame([{"Field": x, "Type": "str"} for x in file_records[0][2]])
|
|
355
374
|
|
|
356
375
|
assert response.data_frame.equals(expected_df)
|
|
@@ -2,7 +2,7 @@ import ast
|
|
|
2
2
|
from typing import Dict, Optional, List
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
from litellm import completion, batch_completion, embedding
|
|
5
|
+
from litellm import completion, batch_completion, embedding, acompletion
|
|
6
6
|
import pandas as pd
|
|
7
7
|
|
|
8
8
|
from mindsdb.integrations.libs.base import BaseMLEngine
|
|
@@ -42,10 +42,17 @@ class LiteLLMHandler(BaseMLEngine):
|
|
|
42
42
|
f"https://{args['snowflake_account_id']}.snowflakecomputing.com/api/v2/cortex/inference:complete"
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
from litellm import acompletion
|
|
46
|
-
|
|
47
45
|
return await acompletion(model=model, messages=messages, stream=False, **args)
|
|
48
46
|
|
|
47
|
+
@staticmethod
|
|
48
|
+
def completion(model: str, messages: List[dict], args: dict):
|
|
49
|
+
if model.startswith("snowflake/") and "snowflake_account_id" in args:
|
|
50
|
+
args["api_base"] = (
|
|
51
|
+
f"https://{args['snowflake_account_id']}.snowflakecomputing.com/api/v2/cortex/inference:complete"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return completion(model=model, messages=messages, stream=False, **args)
|
|
55
|
+
|
|
49
56
|
def create(
|
|
50
57
|
self,
|
|
51
58
|
target: str,
|
|
@@ -31,9 +31,7 @@ def _map_type(mysql_type_text: str) -> MYSQL_DATA_TYPE:
|
|
|
31
31
|
try:
|
|
32
32
|
return MYSQL_DATA_TYPE(mysql_type_text.upper())
|
|
33
33
|
except Exception:
|
|
34
|
-
logger.warning(
|
|
35
|
-
f"MySQL handler: unknown type: {mysql_type_text}, use TEXT as fallback."
|
|
36
|
-
)
|
|
34
|
+
logger.warning(f"MySQL handler: unknown type: {mysql_type_text}, use TEXT as fallback.")
|
|
37
35
|
return MYSQL_DATA_TYPE.TEXT
|
|
38
36
|
|
|
39
37
|
|
|
@@ -65,22 +63,23 @@ def _make_table_response(result: list[dict], cursor: mysql.connector.cursor.MySQ
|
|
|
65
63
|
mysql_types.append(reverse_c_type_map[type_int])
|
|
66
64
|
continue
|
|
67
65
|
|
|
68
|
-
if type_int
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
66
|
+
if type_int == C_TYPES.MYSQL_TYPE_BLOB:
|
|
67
|
+
# region determine text/blob type by flags
|
|
68
|
+
# Unfortunately, there is no way to determine particular type of text/blob column by flags.
|
|
69
|
+
# Subtype have to be determined by 8-s element of description tuple, but mysql.conector
|
|
70
|
+
# return the same value for all text types (TINYTEXT, TEXT, MEDIUMTEXT, LONGTEXT), and for
|
|
71
|
+
# all blob types (TINYBLOB, BLOB, MEDIUMBLOB, LONGBLOB).
|
|
72
|
+
if col[7] == 16: # and col[8] == 45
|
|
73
|
+
mysql_types.append(MYSQL_DATA_TYPE.TEXT)
|
|
74
|
+
elif col[7] == 144: # and col[8] == 63
|
|
75
|
+
mysql_types.append(MYSQL_DATA_TYPE.BLOB)
|
|
76
|
+
else:
|
|
77
|
+
logger.debug(f"MySQL handler: unknown type code {col[7]}, use TEXT as fallback.")
|
|
78
|
+
mysql_types.append(MYSQL_DATA_TYPE.TEXT)
|
|
79
|
+
# endregion
|
|
80
80
|
else:
|
|
81
|
-
logger.
|
|
81
|
+
logger.warning(f"MySQL handler: unknown type id={type_int} in column {col[0]}, use TEXT as fallback.")
|
|
82
82
|
mysql_types.append(MYSQL_DATA_TYPE.TEXT)
|
|
83
|
-
# endregion
|
|
84
83
|
|
|
85
84
|
# region cast int and bool to nullable types
|
|
86
85
|
serieses = []
|
|
@@ -88,22 +87,20 @@ def _make_table_response(result: list[dict], cursor: mysql.connector.cursor.MySQ
|
|
|
88
87
|
expected_dtype = None
|
|
89
88
|
column_name = description[i][0]
|
|
90
89
|
if mysql_type in (
|
|
91
|
-
MYSQL_DATA_TYPE.SMALLINT,
|
|
92
|
-
MYSQL_DATA_TYPE.
|
|
90
|
+
MYSQL_DATA_TYPE.SMALLINT,
|
|
91
|
+
MYSQL_DATA_TYPE.INT,
|
|
92
|
+
MYSQL_DATA_TYPE.MEDIUMINT,
|
|
93
|
+
MYSQL_DATA_TYPE.BIGINT,
|
|
94
|
+
MYSQL_DATA_TYPE.TINYINT,
|
|
93
95
|
):
|
|
94
|
-
expected_dtype =
|
|
96
|
+
expected_dtype = "Int64"
|
|
95
97
|
elif mysql_type in (MYSQL_DATA_TYPE.BOOL, MYSQL_DATA_TYPE.BOOLEAN):
|
|
96
|
-
expected_dtype =
|
|
98
|
+
expected_dtype = "boolean"
|
|
97
99
|
serieses.append(pd.Series([row[column_name] for row in result], dtype=expected_dtype, name=description[i][0]))
|
|
98
100
|
df = pd.concat(serieses, axis=1, copy=False)
|
|
99
101
|
# endregion
|
|
100
102
|
|
|
101
|
-
response = Response(
|
|
102
|
-
RESPONSE_TYPE.TABLE,
|
|
103
|
-
df,
|
|
104
|
-
affected_rows=cursor.rowcount,
|
|
105
|
-
mysql_types=mysql_types
|
|
106
|
-
)
|
|
103
|
+
response = Response(RESPONSE_TYPE.TABLE, df, affected_rows=cursor.rowcount, mysql_types=mysql_types)
|
|
107
104
|
return response
|
|
108
105
|
|
|
109
106
|
|
|
@@ -182,6 +179,9 @@ class MySQLHandler(DatabaseHandler):
|
|
|
182
179
|
config["ssl_cert"] = ssl_cert
|
|
183
180
|
if ssl_key is not None:
|
|
184
181
|
config["ssl_key"] = ssl_key
|
|
182
|
+
elif ssl is False:
|
|
183
|
+
config["ssl_disabled"] = True
|
|
184
|
+
|
|
185
185
|
if "collation" not in config:
|
|
186
186
|
config["collation"] = "utf8mb4_general_ci"
|
|
187
187
|
if "use_pure" not in config:
|
|
@@ -219,9 +219,7 @@ class MySQLHandler(DatabaseHandler):
|
|
|
219
219
|
connection = self.connect()
|
|
220
220
|
result.success = connection.is_connected()
|
|
221
221
|
except mysql.connector.Error as e:
|
|
222
|
-
logger.error(
|
|
223
|
-
f'Error connecting to MySQL {self.connection_data["database"]}, {e}!'
|
|
224
|
-
)
|
|
222
|
+
logger.error(f"Error connecting to MySQL {self.connection_data['database']}, {e}!")
|
|
225
223
|
result.error_message = str(e)
|
|
226
224
|
|
|
227
225
|
if result.success and need_to_close:
|
|
@@ -252,9 +250,7 @@ class MySQLHandler(DatabaseHandler):
|
|
|
252
250
|
else:
|
|
253
251
|
response = Response(RESPONSE_TYPE.OK, affected_rows=cur.rowcount)
|
|
254
252
|
except mysql.connector.Error as e:
|
|
255
|
-
logger.error(
|
|
256
|
-
f'Error running query: {query} on {self.connection_data["database"]}!'
|
|
257
|
-
)
|
|
253
|
+
logger.error(f"Error running query: {query} on {self.connection_data['database']}!")
|
|
258
254
|
response = Response(RESPONSE_TYPE.ERROR, error_message=str(e))
|
|
259
255
|
if connection is not None and connection.is_connected():
|
|
260
256
|
connection.rollback()
|