MindsDB 25.9.2.0a1__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/file_handler/file_handler.py +7 -0
- mindsdb/integrations/handlers/lightwood_handler/functions.py +45 -79
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +13 -1
- 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 +50 -23
- 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/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 +7 -6
- mindsdb/utilities/utils.py +2 -2
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/METADATA +269 -264
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/RECORD +115 -115
- mindsdb/api/mysql/mysql_proxy/utilities/exceptions.py +0 -14
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/WHEEL +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.9.2.0a1.dist-info → mindsdb-25.9.3rc1.dist-info}/top_level.txt +0 -0
|
@@ -19,74 +19,69 @@ from mindsdb_sql_parser import parse_sql, ParsingException
|
|
|
19
19
|
from mindsdb_sql_parser.ast import CreateTable, DropTables
|
|
20
20
|
from mindsdb.utilities.exception import EntityNotExistsError
|
|
21
21
|
from mindsdb.integrations.libs.response import HandlerStatusResponse
|
|
22
|
+
from mindsdb.utilities import log
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
logger = log.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@ns_conf.route("/")
|
|
25
29
|
class DatabasesResource(Resource):
|
|
26
|
-
@ns_conf.doc(
|
|
27
|
-
@api_endpoint_metrics(
|
|
30
|
+
@ns_conf.doc("list_databases")
|
|
31
|
+
@api_endpoint_metrics("GET", "/databases")
|
|
28
32
|
def get(self):
|
|
29
|
-
|
|
33
|
+
"""List all databases"""
|
|
30
34
|
session = SessionController()
|
|
31
35
|
return session.database_controller.get_list()
|
|
32
36
|
|
|
33
|
-
@ns_conf.doc(
|
|
34
|
-
@api_endpoint_metrics(
|
|
37
|
+
@ns_conf.doc("create_database")
|
|
38
|
+
@api_endpoint_metrics("POST", "/databases")
|
|
35
39
|
def post(self):
|
|
36
|
-
|
|
37
|
-
if
|
|
40
|
+
"""Create a database"""
|
|
41
|
+
if "database" not in request.json:
|
|
38
42
|
return http_error(
|
|
39
|
-
HTTPStatus.BAD_REQUEST,
|
|
40
|
-
'Must provide "database" parameter in POST body'
|
|
43
|
+
HTTPStatus.BAD_REQUEST, "Wrong argument", 'Must provide "database" parameter in POST body'
|
|
41
44
|
)
|
|
42
|
-
check_connection = request.json.get(
|
|
45
|
+
check_connection = request.json.get("check_connection", False)
|
|
43
46
|
session = SessionController()
|
|
44
|
-
database = request.json[
|
|
47
|
+
database = request.json["database"]
|
|
45
48
|
parameters = {}
|
|
46
|
-
if
|
|
47
|
-
return http_error(
|
|
48
|
-
|
|
49
|
-
'Missing "name" field for database'
|
|
50
|
-
)
|
|
51
|
-
if 'engine' not in database:
|
|
49
|
+
if "name" not in database:
|
|
50
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "name" field for database')
|
|
51
|
+
if "engine" not in database:
|
|
52
52
|
return http_error(
|
|
53
|
-
HTTPStatus.BAD_REQUEST,
|
|
54
|
-
|
|
53
|
+
HTTPStatus.BAD_REQUEST,
|
|
54
|
+
"Wrong argument",
|
|
55
|
+
'Missing "engine" field for database. If you want to create a project instead, use the /api/projects endpoint.',
|
|
55
56
|
)
|
|
56
|
-
if
|
|
57
|
-
parameters = database[
|
|
58
|
-
name = database[
|
|
57
|
+
if "parameters" in database:
|
|
58
|
+
parameters = database["parameters"]
|
|
59
|
+
name = database["name"]
|
|
59
60
|
|
|
60
61
|
if session.database_controller.exists(name):
|
|
61
|
-
return http_error(
|
|
62
|
-
HTTPStatus.CONFLICT, 'Name conflict',
|
|
63
|
-
f'Database with name {name} already exists.'
|
|
64
|
-
)
|
|
62
|
+
return http_error(HTTPStatus.CONFLICT, "Name conflict", f"Database with name {name} already exists.")
|
|
65
63
|
|
|
66
64
|
storage = None
|
|
67
65
|
if check_connection:
|
|
68
66
|
try:
|
|
69
|
-
handler = session.integration_controller.create_tmp_handler(name, database[
|
|
67
|
+
handler = session.integration_controller.create_tmp_handler(name, database["engine"], parameters)
|
|
70
68
|
status = handler.check_connection()
|
|
71
69
|
except ImportError as import_error:
|
|
72
70
|
status = HandlerStatusResponse(success=False, error_message=str(import_error))
|
|
73
71
|
|
|
74
72
|
if status.success is not True:
|
|
75
|
-
if hasattr(status,
|
|
73
|
+
if hasattr(status, "redirect_url") and isinstance(status, str):
|
|
76
74
|
return {
|
|
77
75
|
"status": "redirect_required",
|
|
78
76
|
"redirect_url": status.redirect_url,
|
|
79
|
-
"detail": status.error_message
|
|
77
|
+
"detail": status.error_message,
|
|
80
78
|
}, HTTPStatus.OK
|
|
81
|
-
return {
|
|
82
|
-
"status": "connection_error",
|
|
83
|
-
"detail": status.error_message
|
|
84
|
-
}, HTTPStatus.OK
|
|
79
|
+
return {"status": "connection_error", "detail": status.error_message}, HTTPStatus.OK
|
|
85
80
|
|
|
86
81
|
if status.copy_storage:
|
|
87
82
|
storage = handler.handler_storage.export_files()
|
|
88
83
|
|
|
89
|
-
new_integration_id = session.integration_controller.add(name, database[
|
|
84
|
+
new_integration_id = session.integration_controller.add(name, database["engine"], parameters)
|
|
90
85
|
|
|
91
86
|
if storage:
|
|
92
87
|
handler = session.integration_controller.get_data_handler(name, connect=False)
|
|
@@ -96,38 +91,35 @@ class DatabasesResource(Resource):
|
|
|
96
91
|
return new_integration, HTTPStatus.CREATED
|
|
97
92
|
|
|
98
93
|
|
|
99
|
-
@ns_conf.route(
|
|
94
|
+
@ns_conf.route("/status")
|
|
100
95
|
class DatabasesStatusResource(Resource):
|
|
101
|
-
@ns_conf.doc(
|
|
102
|
-
@api_endpoint_metrics(
|
|
96
|
+
@ns_conf.doc("check_database_connection_status")
|
|
97
|
+
@api_endpoint_metrics("POST", "/databases/status")
|
|
103
98
|
def post(self):
|
|
104
|
-
|
|
99
|
+
"""Check the connection parameters for a database"""
|
|
105
100
|
data = {}
|
|
106
|
-
if request.content_type ==
|
|
101
|
+
if request.content_type == "application/json":
|
|
107
102
|
data.update(request.json or {})
|
|
108
|
-
elif request.content_type.startswith(
|
|
103
|
+
elif request.content_type.startswith("multipart/form-data"):
|
|
109
104
|
data.update(request.form or {})
|
|
110
105
|
|
|
111
|
-
if
|
|
112
|
-
return http_error(
|
|
113
|
-
HTTPStatus.BAD_REQUEST, 'Wrong argument',
|
|
114
|
-
'Missing "engine" field for database'
|
|
115
|
-
)
|
|
106
|
+
if "engine" not in data:
|
|
107
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "engine" field for database')
|
|
116
108
|
|
|
117
|
-
engine = data[
|
|
109
|
+
engine = data["engine"]
|
|
118
110
|
parameters = data
|
|
119
|
-
del parameters[
|
|
111
|
+
del parameters["engine"]
|
|
120
112
|
|
|
121
113
|
files = request.files
|
|
122
114
|
temp_dir = None
|
|
123
115
|
if files is not None and len(files) > 0:
|
|
124
|
-
temp_dir = tempfile.mkdtemp(prefix=
|
|
116
|
+
temp_dir = tempfile.mkdtemp(prefix="integration_files_")
|
|
125
117
|
for key, file in files.items():
|
|
126
118
|
temp_dir_path = Path(temp_dir)
|
|
127
119
|
file_name = Path(file.filename)
|
|
128
120
|
file_path = temp_dir_path.joinpath(file_name).resolve()
|
|
129
121
|
if temp_dir_path not in file_path.parents:
|
|
130
|
-
raise Exception(f
|
|
122
|
+
raise Exception(f"Can not save file at path: {file_path}")
|
|
131
123
|
file.save(file_path)
|
|
132
124
|
parameters[key] = str(file_path)
|
|
133
125
|
|
|
@@ -145,102 +137,87 @@ class DatabasesStatusResource(Resource):
|
|
|
145
137
|
shutil.rmtree(temp_dir)
|
|
146
138
|
|
|
147
139
|
if not status.success:
|
|
148
|
-
if hasattr(status,
|
|
140
|
+
if hasattr(status, "redirect_url") and isinstance(status, str):
|
|
149
141
|
return {
|
|
150
142
|
"status": "redirect_required",
|
|
151
143
|
"redirect_url": status.redirect_url,
|
|
152
|
-
"detail": status.error_message
|
|
144
|
+
"detail": status.error_message,
|
|
153
145
|
}, HTTPStatus.OK
|
|
154
|
-
return {
|
|
155
|
-
"status": "connection_error",
|
|
156
|
-
"detail": status.error_message
|
|
157
|
-
}, HTTPStatus.OK
|
|
146
|
+
return {"status": "connection_error", "detail": status.error_message}, HTTPStatus.OK
|
|
158
147
|
|
|
159
148
|
return {
|
|
160
149
|
"status": "success",
|
|
161
150
|
}, HTTPStatus.OK
|
|
162
151
|
|
|
163
152
|
|
|
164
|
-
@ns_conf.route(
|
|
153
|
+
@ns_conf.route("/<database_name>")
|
|
165
154
|
class DatabaseResource(Resource):
|
|
166
|
-
@ns_conf.doc(
|
|
167
|
-
@api_endpoint_metrics(
|
|
155
|
+
@ns_conf.doc("get_database")
|
|
156
|
+
@api_endpoint_metrics("GET", "/databases/database")
|
|
168
157
|
def get(self, database_name):
|
|
169
|
-
|
|
158
|
+
"""Gets a database by name"""
|
|
170
159
|
session = SessionController()
|
|
171
|
-
check_connection = request.args.get(
|
|
160
|
+
check_connection = request.args.get("check_connection", "false").lower() in ("1", "true")
|
|
172
161
|
try:
|
|
173
162
|
project = session.database_controller.get_project(database_name)
|
|
174
|
-
result = {
|
|
175
|
-
'name': database_name,
|
|
176
|
-
'type': 'project',
|
|
177
|
-
'id': project.id,
|
|
178
|
-
'engine': None
|
|
179
|
-
}
|
|
163
|
+
result = {"name": database_name, "type": "project", "id": project.id, "engine": None}
|
|
180
164
|
if check_connection:
|
|
181
|
-
result[
|
|
182
|
-
'success': True,
|
|
183
|
-
'error_message': None
|
|
184
|
-
}
|
|
165
|
+
result["connection_status"] = {"success": True, "error_message": None}
|
|
185
166
|
except (ValueError, EntityNotExistsError):
|
|
186
167
|
integration = session.integration_controller.get(database_name)
|
|
187
168
|
if integration is None:
|
|
188
169
|
return http_error(
|
|
189
|
-
HTTPStatus.NOT_FOUND,
|
|
190
|
-
f'Database with name {database_name} does not exist.'
|
|
170
|
+
HTTPStatus.NOT_FOUND, "Database not found", f"Database with name {database_name} does not exist."
|
|
191
171
|
)
|
|
192
172
|
result = integration
|
|
193
173
|
if check_connection:
|
|
194
|
-
integration[
|
|
195
|
-
'success': False,
|
|
196
|
-
'error_message': None
|
|
197
|
-
}
|
|
174
|
+
integration["connection_status"] = {"success": False, "error_message": None}
|
|
198
175
|
try:
|
|
199
176
|
handler = session.integration_controller.get_data_handler(database_name)
|
|
200
177
|
status = handler.check_connection()
|
|
201
|
-
integration[
|
|
202
|
-
integration[
|
|
178
|
+
integration["connection_status"]["success"] = status.success
|
|
179
|
+
integration["connection_status"]["error_message"] = status.error_message
|
|
203
180
|
except Exception as e:
|
|
204
|
-
integration[
|
|
205
|
-
integration[
|
|
181
|
+
integration["connection_status"]["success"] = False
|
|
182
|
+
integration["connection_status"]["error_message"] = str(e)
|
|
206
183
|
|
|
207
184
|
return result
|
|
208
185
|
|
|
209
|
-
@ns_conf.doc(
|
|
210
|
-
@api_endpoint_metrics(
|
|
186
|
+
@ns_conf.doc("update_database")
|
|
187
|
+
@api_endpoint_metrics("PUT", "/databases/database")
|
|
211
188
|
def put(self, database_name):
|
|
212
|
-
|
|
213
|
-
if
|
|
189
|
+
"""Updates or creates a database"""
|
|
190
|
+
if "database" not in request.json:
|
|
214
191
|
return http_error(
|
|
215
|
-
HTTPStatus.BAD_REQUEST,
|
|
216
|
-
'Must provide "database" parameter in POST body'
|
|
192
|
+
HTTPStatus.BAD_REQUEST, "Wrong argument", 'Must provide "database" parameter in POST body'
|
|
217
193
|
)
|
|
218
194
|
|
|
219
195
|
session = SessionController()
|
|
220
196
|
parameters = {}
|
|
221
|
-
database = request.json[
|
|
222
|
-
check_connection = request.json.get(
|
|
197
|
+
database = request.json["database"]
|
|
198
|
+
check_connection = request.json.get("check_connection", False)
|
|
223
199
|
|
|
224
|
-
if
|
|
225
|
-
parameters = database[
|
|
200
|
+
if "parameters" in database:
|
|
201
|
+
parameters = database["parameters"]
|
|
226
202
|
if not session.database_controller.exists(database_name):
|
|
227
203
|
# Create.
|
|
228
|
-
if
|
|
204
|
+
if "engine" not in database:
|
|
229
205
|
return http_error(
|
|
230
|
-
HTTPStatus.BAD_REQUEST,
|
|
206
|
+
HTTPStatus.BAD_REQUEST,
|
|
207
|
+
"Wrong argument",
|
|
231
208
|
'Missing "engine" field for new database. '
|
|
232
|
-
|
|
209
|
+
"If you want to create a project instead, use the POST /api/projects endpoint.",
|
|
233
210
|
)
|
|
234
|
-
new_integration_id = session.integration_controller.add(database_name, database[
|
|
211
|
+
new_integration_id = session.integration_controller.add(database_name, database["engine"], parameters)
|
|
235
212
|
new_integration = session.database_controller.get_integration(new_integration_id)
|
|
236
213
|
return new_integration, HTTPStatus.CREATED
|
|
237
214
|
|
|
238
215
|
if check_connection:
|
|
239
216
|
existing_integration = session.integration_controller.get(database_name)
|
|
240
|
-
temp_name = f
|
|
217
|
+
temp_name = f"{database_name}_{time.time()}".replace(".", "")
|
|
241
218
|
try:
|
|
242
219
|
handler = session.integration_controller.create_tmp_handler(
|
|
243
|
-
temp_name, existing_integration[
|
|
220
|
+
temp_name, existing_integration["engine"], parameters
|
|
244
221
|
)
|
|
245
222
|
status = handler.check_connection()
|
|
246
223
|
except ImportError as import_error:
|
|
@@ -248,94 +225,81 @@ class DatabaseResource(Resource):
|
|
|
248
225
|
|
|
249
226
|
if status.success is not True:
|
|
250
227
|
return http_error(
|
|
251
|
-
HTTPStatus.BAD_REQUEST,
|
|
252
|
-
status.error_message or 'Connection error'
|
|
228
|
+
HTTPStatus.BAD_REQUEST, "Connection error", status.error_message or "Connection error"
|
|
253
229
|
)
|
|
254
230
|
|
|
255
231
|
session.integration_controller.modify(database_name, parameters)
|
|
256
232
|
return session.integration_controller.get(database_name)
|
|
257
233
|
|
|
258
|
-
@ns_conf.doc(
|
|
259
|
-
@api_endpoint_metrics(
|
|
234
|
+
@ns_conf.doc("delete_database")
|
|
235
|
+
@api_endpoint_metrics("DELETE", "/databases/database")
|
|
260
236
|
def delete(self, database_name):
|
|
261
|
-
|
|
237
|
+
"""Deletes a database by name"""
|
|
262
238
|
session = SessionController()
|
|
263
239
|
if not session.database_controller.exists(database_name):
|
|
264
240
|
return http_error(
|
|
265
|
-
HTTPStatus.NOT_FOUND,
|
|
266
|
-
f'Database with name {database_name} does not exist.'
|
|
241
|
+
HTTPStatus.NOT_FOUND, "Database not found", f"Database with name {database_name} does not exist."
|
|
267
242
|
)
|
|
268
243
|
try:
|
|
269
244
|
session.database_controller.delete(database_name)
|
|
270
245
|
except Exception as e:
|
|
246
|
+
logger.debug(f"Error while deleting database '{database_name}'", exc_info=True)
|
|
271
247
|
return http_error(
|
|
272
|
-
HTTPStatus.BAD_REQUEST,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
+
|
|
276
|
-
+
|
|
248
|
+
HTTPStatus.BAD_REQUEST,
|
|
249
|
+
"Error",
|
|
250
|
+
f"Cannot delete database {database_name}. "
|
|
251
|
+
+ "This is most likely a system database, a permanent integration, or an ML engine with active models. "
|
|
252
|
+
+ f"Full error: {e}. "
|
|
253
|
+
+ "Please check the name and try again.",
|
|
277
254
|
)
|
|
278
|
-
return
|
|
255
|
+
return "", HTTPStatus.NO_CONTENT
|
|
279
256
|
|
|
280
257
|
|
|
281
258
|
def _tables_row_to_obj(table_row: TablesRow) -> Dict:
|
|
282
259
|
type = table_row.TABLE_TYPE.lower()
|
|
283
|
-
if table_row.TABLE_TYPE ==
|
|
284
|
-
type =
|
|
285
|
-
return {
|
|
286
|
-
'name': table_row.TABLE_NAME,
|
|
287
|
-
'type': type
|
|
288
|
-
}
|
|
260
|
+
if table_row.TABLE_TYPE == "BASE TABLE":
|
|
261
|
+
type = "data"
|
|
262
|
+
return {"name": table_row.TABLE_NAME, "type": type}
|
|
289
263
|
|
|
290
264
|
|
|
291
|
-
@ns_conf.route(
|
|
265
|
+
@ns_conf.route("/<database_name>/tables")
|
|
292
266
|
class TablesList(Resource):
|
|
293
|
-
@ns_conf.doc(
|
|
294
|
-
@api_endpoint_metrics(
|
|
267
|
+
@ns_conf.doc("list_tables")
|
|
268
|
+
@api_endpoint_metrics("GET", "/databases/database/tables")
|
|
295
269
|
def get(self, database_name):
|
|
296
|
-
|
|
270
|
+
"""Get all tables in a database"""
|
|
297
271
|
session = SessionController()
|
|
298
272
|
datanode = session.datahub.get(database_name)
|
|
299
273
|
all_tables = datanode.get_tables()
|
|
300
274
|
table_objs = [_tables_row_to_obj(t) for t in all_tables]
|
|
301
275
|
return table_objs
|
|
302
276
|
|
|
303
|
-
@ns_conf.doc(
|
|
304
|
-
@api_endpoint_metrics(
|
|
277
|
+
@ns_conf.doc("create_table")
|
|
278
|
+
@api_endpoint_metrics("POST", "/databases/database/tables")
|
|
305
279
|
def post(self, database_name):
|
|
306
|
-
|
|
307
|
-
if
|
|
308
|
-
return http_error(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
'Missing "name" field for table'
|
|
317
|
-
)
|
|
318
|
-
if 'select' not in table:
|
|
319
|
-
return http_error(
|
|
320
|
-
HTTPStatus.BAD_REQUEST, 'Wrong argument',
|
|
321
|
-
'Missing "select" field for table'
|
|
322
|
-
)
|
|
323
|
-
table_name = table['name']
|
|
324
|
-
select_query = table['select']
|
|
280
|
+
"""Creates a table in a database"""
|
|
281
|
+
if "table" not in request.json:
|
|
282
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Must provide "table" parameter in POST body')
|
|
283
|
+
table = request.json["table"]
|
|
284
|
+
if "name" not in table:
|
|
285
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "name" field for table')
|
|
286
|
+
if "select" not in table:
|
|
287
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "select" field for table')
|
|
288
|
+
table_name = table["name"]
|
|
289
|
+
select_query = table["select"]
|
|
325
290
|
replace = False
|
|
326
|
-
if
|
|
327
|
-
replace = table[
|
|
291
|
+
if "replace" in table:
|
|
292
|
+
replace = table["replace"]
|
|
328
293
|
|
|
329
294
|
session = SessionController()
|
|
330
295
|
try:
|
|
331
296
|
session.database_controller.get_project(database_name)
|
|
332
|
-
error_message =
|
|
333
|
-
|
|
334
|
-
+ f
|
|
335
|
-
|
|
336
|
-
HTTPStatus.BAD_REQUEST, 'Error',
|
|
337
|
-
error_message
|
|
297
|
+
error_message = (
|
|
298
|
+
f"Database {database_name} is a project. "
|
|
299
|
+
+ f"If you want to create a model or view, use the projects/{database_name}/models/{table_name} or "
|
|
300
|
+
+ f"projects/{database_name}/views/{table_name} endpoints instead."
|
|
338
301
|
)
|
|
302
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Error", error_message)
|
|
339
303
|
except EntityNotExistsError:
|
|
340
304
|
# Only support creating tables from integrations.
|
|
341
305
|
pass
|
|
@@ -343,40 +307,26 @@ class TablesList(Resource):
|
|
|
343
307
|
datanode = session.datahub.get(database_name)
|
|
344
308
|
if datanode is None:
|
|
345
309
|
return http_error(
|
|
346
|
-
HTTPStatus.NOT_FOUND,
|
|
347
|
-
f'Database with name {database_name} does not exist'
|
|
310
|
+
HTTPStatus.NOT_FOUND, "Database not exists", f"Database with name {database_name} does not exist"
|
|
348
311
|
)
|
|
349
312
|
all_tables = datanode.get_tables()
|
|
350
313
|
for t in all_tables:
|
|
351
314
|
if t.TABLE_NAME == table_name and not replace:
|
|
352
|
-
return http_error(
|
|
353
|
-
HTTPStatus.CONFLICT, 'Name conflict',
|
|
354
|
-
f'Table with name {table_name} already exists'
|
|
355
|
-
)
|
|
315
|
+
return http_error(HTTPStatus.CONFLICT, "Name conflict", f"Table with name {table_name} already exists")
|
|
356
316
|
|
|
357
317
|
try:
|
|
358
318
|
select_ast = parse_sql(select_query)
|
|
359
319
|
except ParsingException:
|
|
360
|
-
return http_error(
|
|
361
|
-
HTTPStatus.BAD_REQUEST, 'Error',
|
|
362
|
-
f'Could not parse select statement {select_query}'
|
|
363
|
-
)
|
|
320
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Error", f"Could not parse select statement {select_query}")
|
|
364
321
|
|
|
365
|
-
create_ast = CreateTable(
|
|
366
|
-
f'{database_name}.{table_name}',
|
|
367
|
-
from_select=select_ast,
|
|
368
|
-
is_replace=replace
|
|
369
|
-
)
|
|
322
|
+
create_ast = CreateTable(f"{database_name}.{table_name}", from_select=select_ast, is_replace=replace)
|
|
370
323
|
|
|
371
324
|
mysql_proxy = FakeMysqlProxy()
|
|
372
325
|
|
|
373
326
|
try:
|
|
374
327
|
mysql_proxy.process_query(create_ast.get_string())
|
|
375
328
|
except Exception as e:
|
|
376
|
-
return http_error(
|
|
377
|
-
HTTPStatus.BAD_REQUEST, 'Error',
|
|
378
|
-
str(e)
|
|
379
|
-
)
|
|
329
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Error", str(e))
|
|
380
330
|
|
|
381
331
|
all_tables = datanode.get_tables()
|
|
382
332
|
try:
|
|
@@ -384,17 +334,16 @@ class TablesList(Resource):
|
|
|
384
334
|
return _tables_row_to_obj(matching_table), HTTPStatus.CREATED
|
|
385
335
|
except StopIteration:
|
|
386
336
|
return http_error(
|
|
387
|
-
HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
388
|
-
f'Table with name {table_name} could not be created'
|
|
337
|
+
HTTPStatus.INTERNAL_SERVER_ERROR, "Error", f"Table with name {table_name} could not be created"
|
|
389
338
|
)
|
|
390
339
|
|
|
391
340
|
|
|
392
|
-
@ns_conf.route(
|
|
393
|
-
@ns_conf.param(
|
|
394
|
-
@ns_conf.param(
|
|
341
|
+
@ns_conf.route("/<database_name>/tables/<table_name>")
|
|
342
|
+
@ns_conf.param("database_name", "Name of the database")
|
|
343
|
+
@ns_conf.param("table_name", "Name of the table")
|
|
395
344
|
class TableResource(Resource):
|
|
396
|
-
@ns_conf.doc(
|
|
397
|
-
@api_endpoint_metrics(
|
|
345
|
+
@ns_conf.doc("get_table")
|
|
346
|
+
@api_endpoint_metrics("GET", "/databases/database/tables/table")
|
|
398
347
|
def get(self, database_name, table_name):
|
|
399
348
|
session = SessionController()
|
|
400
349
|
datanode = session.datahub.get(database_name)
|
|
@@ -403,21 +352,20 @@ class TableResource(Resource):
|
|
|
403
352
|
matching_table = next(t for t in all_tables if t.TABLE_NAME == table_name)
|
|
404
353
|
return _tables_row_to_obj(matching_table)
|
|
405
354
|
except StopIteration:
|
|
406
|
-
return http_error(
|
|
407
|
-
HTTPStatus.NOT_FOUND, 'Table not found',
|
|
408
|
-
f'Table with name {table_name} not found'
|
|
409
|
-
)
|
|
355
|
+
return http_error(HTTPStatus.NOT_FOUND, "Table not found", f"Table with name {table_name} not found")
|
|
410
356
|
|
|
411
|
-
@ns_conf.doc(
|
|
412
|
-
@api_endpoint_metrics(
|
|
357
|
+
@ns_conf.doc("drop_table")
|
|
358
|
+
@api_endpoint_metrics("DELETE", "/databases/database/tables/table")
|
|
413
359
|
def delete(self, database_name, table_name):
|
|
414
360
|
session = SessionController()
|
|
415
361
|
try:
|
|
416
362
|
session.database_controller.get_project(database_name)
|
|
417
|
-
error_message =
|
|
418
|
-
|
|
419
|
-
+ f
|
|
420
|
-
|
|
363
|
+
error_message = (
|
|
364
|
+
f"Database {database_name} is a project. "
|
|
365
|
+
+ f"If you want to delete a model or view, use the projects/{database_name}/models/{table_name} or "
|
|
366
|
+
+ f"projects/{database_name}/views/{table_name} endpoints instead."
|
|
367
|
+
)
|
|
368
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Error", error_message)
|
|
421
369
|
except EntityNotExistsError:
|
|
422
370
|
# Only support dropping tables from integrations.
|
|
423
371
|
pass
|
|
@@ -425,37 +373,28 @@ class TableResource(Resource):
|
|
|
425
373
|
datanode = session.datahub.get(database_name)
|
|
426
374
|
if datanode is None:
|
|
427
375
|
return http_error(
|
|
428
|
-
HTTPStatus.NOT_FOUND,
|
|
429
|
-
f'Database with name {database_name} not found'
|
|
376
|
+
HTTPStatus.NOT_FOUND, "Database not found", f"Database with name {database_name} not found"
|
|
430
377
|
)
|
|
431
378
|
all_tables = datanode.get_tables()
|
|
432
379
|
try:
|
|
433
380
|
next(t for t in all_tables if t.TABLE_NAME == table_name)
|
|
434
381
|
except StopIteration:
|
|
435
|
-
return http_error(
|
|
436
|
-
HTTPStatus.NOT_FOUND, 'Table not found',
|
|
437
|
-
f'Table with name {table_name} not found'
|
|
438
|
-
)
|
|
382
|
+
return http_error(HTTPStatus.NOT_FOUND, "Table not found", f"Table with name {table_name} not found")
|
|
439
383
|
|
|
440
|
-
drop_ast = DropTables(
|
|
441
|
-
tables=[table_name],
|
|
442
|
-
if_exists=True
|
|
443
|
-
)
|
|
384
|
+
drop_ast = DropTables(tables=[table_name], if_exists=True)
|
|
444
385
|
|
|
445
386
|
try:
|
|
446
387
|
integration_handler = session.integration_controller.get_data_handler(database_name)
|
|
447
388
|
except Exception:
|
|
448
389
|
return http_error(
|
|
449
|
-
HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
450
|
-
f'Could not get database handler for {database_name}'
|
|
390
|
+
HTTPStatus.INTERNAL_SERVER_ERROR, "Error", f"Could not get database handler for {database_name}"
|
|
451
391
|
)
|
|
452
392
|
try:
|
|
453
393
|
result = integration_handler.query(drop_ast)
|
|
454
394
|
except NotImplementedError:
|
|
455
395
|
return http_error(
|
|
456
|
-
HTTPStatus.BAD_REQUEST,
|
|
457
|
-
f'Database {database_name} does not support dropping tables.'
|
|
396
|
+
HTTPStatus.BAD_REQUEST, "Error", f"Database {database_name} does not support dropping tables."
|
|
458
397
|
)
|
|
459
398
|
if result.type == RESPONSE_TYPE.ERROR:
|
|
460
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
461
|
-
return
|
|
399
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Error", result.error_message)
|
|
400
|
+
return "", HTTPStatus.NO_CONTENT
|
|
@@ -160,7 +160,11 @@ class File(Resource):
|
|
|
160
160
|
"Сan't determine remote file size",
|
|
161
161
|
)
|
|
162
162
|
if file_size > MAX_FILE_SIZE:
|
|
163
|
-
return http_error(
|
|
163
|
+
return http_error(
|
|
164
|
+
400,
|
|
165
|
+
"File is too big",
|
|
166
|
+
f"Upload limit for file is {MAX_FILE_SIZE >> 20} MB",
|
|
167
|
+
)
|
|
164
168
|
with requests.get(url, stream=True) as r:
|
|
165
169
|
if r.status_code != 200:
|
|
166
170
|
return http_error(400, "Error getting file", f"Got status code: {r.status_code}")
|
|
@@ -209,11 +213,18 @@ class File(Resource):
|
|
|
209
213
|
|
|
210
214
|
try:
|
|
211
215
|
ca.file_controller.delete_file(name)
|
|
212
|
-
except
|
|
213
|
-
logger.
|
|
216
|
+
except FileNotFoundError:
|
|
217
|
+
logger.exception(f"Error when deleting file '{name}'")
|
|
214
218
|
return http_error(
|
|
215
219
|
400,
|
|
216
220
|
"Error deleting file",
|
|
217
|
-
f"There was an error while
|
|
221
|
+
f"There was an error while trying to delete file with name '{name}'",
|
|
222
|
+
)
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(e)
|
|
225
|
+
return http_error(
|
|
226
|
+
500,
|
|
227
|
+
"Error occured while deleting file",
|
|
228
|
+
f"There was an error while trying to delete file with name '{name}'",
|
|
218
229
|
)
|
|
219
230
|
return "", 200
|
|
@@ -19,6 +19,9 @@ from mindsdb.api.http.utils import http_error
|
|
|
19
19
|
from mindsdb.api.http.namespaces.configs.handlers import ns_conf
|
|
20
20
|
from mindsdb.api.executor.controllers.session_controller import SessionController
|
|
21
21
|
from mindsdb.api.executor.command_executor import ExecuteCommands
|
|
22
|
+
from mindsdb.utilities import log
|
|
23
|
+
|
|
24
|
+
logger = log.getLogger(__name__)
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
@ns_conf.route("/")
|
|
@@ -59,7 +62,9 @@ class HandlerIcon(Resource):
|
|
|
59
62
|
if icon_path.is_absolute() is False:
|
|
60
63
|
icon_path = Path(os.getcwd()).joinpath(icon_path)
|
|
61
64
|
except Exception:
|
|
62
|
-
|
|
65
|
+
error_message = f"Icon for '{handler_name}' not found"
|
|
66
|
+
logger.warning(error_message)
|
|
67
|
+
return http_error(HTTPStatus.NOT_FOUND, "Icon not found", error_message)
|
|
63
68
|
else:
|
|
64
69
|
return send_file(icon_path)
|
|
65
70
|
|
|
@@ -170,7 +175,7 @@ class BYOMUpload(Resource):
|
|
|
170
175
|
code_file_path = params["code"]
|
|
171
176
|
try:
|
|
172
177
|
module_file_path = params["modules"]
|
|
173
|
-
except
|
|
178
|
+
except KeyError:
|
|
174
179
|
module_file_path = Path(code_file_path).parent / "requirements.txt"
|
|
175
180
|
module_file_path.touch()
|
|
176
181
|
module_file_path = str(module_file_path)
|