MindsDB 25.7.2.0__py3-none-any.whl → 25.7.4.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.

Files changed (69) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +1 -1
  3. mindsdb/api/a2a/common/server/server.py +16 -6
  4. mindsdb/api/executor/command_executor.py +213 -137
  5. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -1
  6. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  7. mindsdb/api/executor/planner/plan_join.py +3 -0
  8. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  9. mindsdb/api/executor/planner/query_planner.py +1 -0
  10. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  11. mindsdb/api/http/initialize.py +16 -43
  12. mindsdb/api/http/namespaces/agents.py +24 -21
  13. mindsdb/api/http/namespaces/chatbots.py +83 -120
  14. mindsdb/api/http/namespaces/file.py +1 -1
  15. mindsdb/api/http/namespaces/jobs.py +38 -60
  16. mindsdb/api/http/namespaces/tree.py +69 -61
  17. mindsdb/api/mcp/start.py +2 -0
  18. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  19. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  20. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  21. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  22. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  23. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  25. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  26. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -76
  27. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  28. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +16 -3
  29. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  30. mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
  31. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  32. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  33. mindsdb/integrations/handlers/s3_handler/s3_handler.py +72 -70
  34. mindsdb/integrations/handlers/salesforce_handler/constants.py +208 -0
  35. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +142 -81
  36. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +12 -4
  37. mindsdb/integrations/handlers/slack_handler/slack_tables.py +141 -161
  38. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  39. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  40. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  41. mindsdb/integrations/handlers/youtube_handler/youtube_tables.py +183 -55
  42. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  43. mindsdb/integrations/utilities/handler_utils.py +32 -12
  44. mindsdb/interfaces/agents/agents_controller.py +169 -110
  45. mindsdb/interfaces/agents/langchain_agent.py +10 -3
  46. mindsdb/interfaces/data_catalog/data_catalog_loader.py +22 -8
  47. mindsdb/interfaces/database/database.py +38 -13
  48. mindsdb/interfaces/database/integrations.py +20 -5
  49. mindsdb/interfaces/database/projects.py +63 -16
  50. mindsdb/interfaces/database/views.py +86 -60
  51. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  52. mindsdb/interfaces/knowledge_base/controller.py +33 -5
  53. mindsdb/interfaces/knowledge_base/evaluate.py +53 -9
  54. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  55. mindsdb/interfaces/knowledge_base/llm_client.py +3 -3
  56. mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +21 -13
  57. mindsdb/interfaces/query_context/context_controller.py +100 -133
  58. mindsdb/interfaces/skills/skills_controller.py +18 -6
  59. mindsdb/interfaces/storage/db.py +40 -6
  60. mindsdb/interfaces/variables/variables_controller.py +8 -15
  61. mindsdb/utilities/config.py +3 -3
  62. mindsdb/utilities/functions.py +72 -60
  63. mindsdb/utilities/log.py +38 -6
  64. mindsdb/utilities/ps.py +7 -7
  65. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/METADATA +262 -263
  66. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/RECORD +69 -68
  67. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/WHEEL +0 -0
  68. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {mindsdb-25.7.2.0.dist-info → mindsdb-25.7.4.0.dist-info}/top_level.txt +0 -0
@@ -18,18 +18,30 @@ class DatabaseController:
18
18
  self.logs_db_controller = LogDBController()
19
19
  self.information_schema_controller = None
20
20
 
21
- def delete(self, name: str):
21
+ def delete(self, name: str, strict_case: bool = False) -> None:
22
+ """Delete a database (project or integration) by name.
23
+
24
+ Args:
25
+ name (str): The name of the database to delete.
26
+ strict_case (bool, optional): If True, the database name is case-sensitive. Defaults to False.
27
+
28
+ Raises:
29
+ EntityNotExistsError: If the database does not exist.
30
+ Exception: If the database cannot be deleted.
31
+
32
+ Returns:
33
+ None
34
+ """
22
35
  databases = self.get_dict()
23
- name = name.lower()
24
- if name not in databases:
36
+ if name.lower() not in databases:
25
37
  raise EntityNotExistsError("Database does not exists", name)
26
- db_type = databases[name]["type"]
38
+ db_type = databases[name.lower()]["type"]
27
39
  if db_type == "project":
28
- project = self.get_project(name)
40
+ project = self.get_project(name, strict_case)
29
41
  project.delete()
30
42
  return
31
43
  elif db_type == "data":
32
- self.integration_controller.delete(name)
44
+ self.integration_controller.delete(name, strict_case)
33
45
  return
34
46
  else:
35
47
  raise Exception(f"Database with type '{db_type}' cannot be deleted")
@@ -81,9 +93,9 @@ class DatabaseController:
81
93
 
82
94
  return result
83
95
 
84
- def get_dict(self, filter_type: Optional[str] = None):
96
+ def get_dict(self, filter_type: Optional[str] = None, lowercase: bool = True):
85
97
  return OrderedDict(
86
- (x["name"].lower(), {"type": x["type"], "engine": x["engine"], "id": x["id"]})
98
+ (x["name"].lower() if lowercase else x["name"], {"type": x["type"], "engine": x["engine"], "id": x["id"]})
87
99
  for x in self.get_list(filter_type=filter_type)
88
100
  )
89
101
 
@@ -98,8 +110,17 @@ class DatabaseController:
98
110
  def exists(self, db_name: str) -> bool:
99
111
  return db_name.lower() in self.get_dict()
100
112
 
101
- def get_project(self, name: str):
102
- return self.project_controller.get(name=name)
113
+ def get_project(self, name: str, strict_case: bool = False):
114
+ """Get a project by name.
115
+
116
+ Args:
117
+ name (str): The name of the project to retrieve.
118
+ strict_case (bool, optional): If True, the project name is case-sensitive. Defaults to False.
119
+
120
+ Returns:
121
+ Project: The project instance matching the given name.
122
+ """
123
+ return self.project_controller.get(name=name, strict_case=strict_case)
103
124
 
104
125
  def get_system_db(self, name: str):
105
126
  if name == "log":
@@ -112,19 +133,21 @@ class DatabaseController:
112
133
  else:
113
134
  raise Exception(f"Database '{name}' does not exists")
114
135
 
115
- def update(self, name: str, data: dict):
136
+ def update(self, name: str, data: dict, strict_case: bool = False):
116
137
  """
117
138
  Updates the database with the given name using the provided data.
118
139
 
119
140
  Parameters:
120
141
  name (str): The name of the database to update.
121
142
  data (dict): The data to update the database with.
143
+ strict_case (bool): if True, then name is case-sesitive
122
144
 
123
145
  Raises:
124
146
  EntityNotExistsError: If the database does not exist.
125
147
  """
126
- databases = self.get_dict()
127
- name = name.lower()
148
+ databases = self.get_dict(lowercase=(not strict_case))
149
+ if not strict_case:
150
+ name = name.lower()
128
151
  if name not in databases:
129
152
  raise EntityNotExistsError("Database does not exist.", name)
130
153
 
@@ -133,6 +156,8 @@ class DatabaseController:
133
156
  # Only the name of the project can be updated.
134
157
  if {"name"} != set(data):
135
158
  raise ValueError("Only the 'name' field can be updated for projects.")
159
+ if not data["name"].islower():
160
+ raise ValueError("New name must be in lower case.")
136
161
  self.project_controller.update(name=name, new_name=str(data["name"]))
137
162
  return
138
163
 
@@ -161,7 +161,7 @@ class IntegrationController:
161
161
  db.session.commit()
162
162
  return integration_record.id
163
163
 
164
- def add(self, name, engine, connection_args):
164
+ def add(self, name: str, engine, connection_args):
165
165
  logger.debug(
166
166
  "%s: add method calling name=%s, engine=%s, connection_args=%s, company_id=%s",
167
167
  self.__class__.__name__,
@@ -172,6 +172,9 @@ class IntegrationController:
172
172
  )
173
173
  handler_meta = self.get_handler_meta(engine)
174
174
 
175
+ if not name.islower():
176
+ raise ValueError(f"The name must be in lower case: {name}")
177
+
175
178
  accept_connection_args = handler_meta.get("connection_args")
176
179
  logger.debug("%s: accept_connection_args - %s", self.__class__.__name__, accept_connection_args)
177
180
 
@@ -210,20 +213,32 @@ class IntegrationController:
210
213
  integration_record.data = data
211
214
  db.session.commit()
212
215
 
213
- def delete(self, name):
214
- if name in ("files", "lightwood"):
216
+ def delete(self, name: str, strict_case: bool = False) -> None:
217
+ """Delete an integration by name.
218
+
219
+ Args:
220
+ name (str): The name of the integration to delete.
221
+ strict_case (bool, optional): If True, the integration name is case-sensitive. Defaults to False.
222
+
223
+ Raises:
224
+ Exception: If the integration cannot be deleted (system, permanent, demo, in use, or has active models).
225
+
226
+ Returns:
227
+ None
228
+ """
229
+ if name == "files":
215
230
  raise Exception("Unable to drop: is system database")
216
231
 
217
232
  self.handlers_cache.delete(name)
218
233
 
219
234
  # check permanent integration
220
- if name in self.handler_modules:
235
+ if name.lower() in self.handler_modules:
221
236
  handler = self.handler_modules[name]
222
237
 
223
238
  if getattr(handler, "permanent", False) is True:
224
239
  raise Exception("Unable to drop permanent integration")
225
240
 
226
- integration_record = self._get_integration_record(name)
241
+ integration_record = self._get_integration_record(name, case_sensitive=strict_case)
227
242
  if isinstance(integration_record.data, dict) and integration_record.data.get("is_demo") is True:
228
243
  raise Exception("Unable to drop demo object")
229
244
 
@@ -94,14 +94,26 @@ class Project:
94
94
  def drop_model(self, name: str):
95
95
  ModelController().delete_model(name, project_name=self.name)
96
96
 
97
- def drop_view(self, name: str):
98
- ViewController().delete(name, project_name=self.name)
97
+ def drop_view(self, name: str, strict_case: bool = False) -> None:
98
+ """Remove a view with the specified name from the current project.
99
+
100
+ Args:
101
+ name (str): The name of the view to remove.
102
+ strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
103
+
104
+ Raises:
105
+ EntityNotExistsError: If the view does not exist.
106
+
107
+ Returns:
108
+ None
109
+ """
110
+ ViewController().delete(name, project_name=self.name, strict_case=strict_case)
99
111
 
100
112
  def create_view(self, name: str, query: str):
101
113
  ViewController().add(name, query=query, project_name=self.name)
102
114
 
103
- def update_view(self, name: str, query: str):
104
- ViewController().update(name, query=query, project_name=self.name)
115
+ def update_view(self, name: str, query: str, strict_case: bool = False):
116
+ ViewController().update(name, query=query, project_name=self.name, strict_case=strict_case)
105
117
 
106
118
  def delete_view(self, name: str):
107
119
  ViewController().delete(name, project_name=self.name)
@@ -279,18 +291,29 @@ class Project:
279
291
  ]
280
292
  return data
281
293
 
282
- def get_view(self, name):
283
- view_record = (
284
- db.session.query(db.View)
285
- .filter(
286
- db.View.project_id == self.id,
287
- db.View.company_id == ctx.company_id,
288
- sa.func.lower(db.View.name) == name.lower(),
289
- )
290
- .one_or_none()
294
+ def get_view(self, name: str, strict_case: bool = False) -> dict | None:
295
+ """Get a view by name from the current project.
296
+
297
+ Args:
298
+ name (str): The name of the view to retrieve.
299
+ strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
300
+
301
+ Returns:
302
+ dict | None: A dictionary with view information if found, otherwise None.
303
+ """
304
+ query = db.session.query(db.View).filter(
305
+ db.View.project_id == self.id,
306
+ db.View.company_id == ctx.company_id,
291
307
  )
308
+ if strict_case:
309
+ query = query.filter(db.View.name == name)
310
+ else:
311
+ query = query.filter(sa.func.lower(db.View.name) == name.lower())
312
+
313
+ view_record = query.one_or_none()
314
+
292
315
  if view_record is None:
293
- return view_record
316
+ return None
294
317
  return {
295
318
  "name": view_record.name,
296
319
  "query": view_record.query,
@@ -385,8 +408,29 @@ class ProjectController:
385
408
  return [Project.from_record(x) for x in records]
386
409
 
387
410
  def get(
388
- self, id: Optional[int] = None, name: Optional[str] = None, deleted: bool = False, is_default: bool = False
411
+ self,
412
+ id: int | None = None,
413
+ name: str | None = None,
414
+ deleted: bool = False,
415
+ is_default: bool = False,
416
+ strict_case: bool = False,
389
417
  ) -> Project:
418
+ """Get a project by id or name.
419
+
420
+ Args:
421
+ id (int | None, optional): The id of the project to retrieve. Cannot be used with 'name'.
422
+ name (str | None, optional): The name of the project to retrieve. Cannot be used with 'id'.
423
+ deleted (bool, optional): If True, include deleted projects. Defaults to False.
424
+ is_default (bool, optional): If True, only return the default project. Defaults to False.
425
+ strict_case (bool, optional): If True, the project name is case-sensitive. Defaults to False.
426
+
427
+ Raises:
428
+ ValueError: If both 'id' and 'name' are provided.
429
+ EntityNotExistsError: If the project is not found.
430
+
431
+ Returns:
432
+ Project: The project instance matching the given criteria.
433
+ """
390
434
  if id is not None and name is not None:
391
435
  raise ValueError("Both 'id' and 'name' can't be provided at the same time")
392
436
 
@@ -396,7 +440,10 @@ class ProjectController:
396
440
  if id is not None:
397
441
  q = q.filter_by(id=id)
398
442
  elif name is not None:
399
- q = q.filter((sa.func.lower(db.Project.name) == sa.func.lower(name)))
443
+ if strict_case:
444
+ q = q.filter((db.Project.name == name))
445
+ else:
446
+ q = q.filter((sa.func.lower(db.Project.name) == sa.func.lower(name)))
400
447
 
401
448
  if deleted is True:
402
449
  q = q.filter((db.Project.deleted_at != sa.null()))
@@ -12,64 +12,90 @@ class ViewController:
12
12
  from mindsdb.interfaces.database.database import DatabaseController
13
13
 
14
14
  database_controller = DatabaseController()
15
- project_databases_dict = database_controller.get_dict(filter_type='project')
15
+ project_databases_dict = database_controller.get_dict(filter_type="project")
16
16
 
17
17
  if project_name not in project_databases_dict:
18
- raise EntityNotExistsError('Can not find project', project_name)
18
+ raise EntityNotExistsError("Can not find project", project_name)
19
19
 
20
- project_id = project_databases_dict[project_name]['id']
20
+ project_id = project_databases_dict[project_name]["id"]
21
21
  view_record = (
22
22
  db.session.query(db.View.id)
23
23
  .filter(
24
- func.lower(db.View.name) == name,
25
- db.View.company_id == ctx.company_id,
26
- db.View.project_id == project_id
27
- ).first()
24
+ func.lower(db.View.name) == name, db.View.company_id == ctx.company_id, db.View.project_id == project_id
25
+ )
26
+ .first()
28
27
  )
29
28
  if view_record is not None:
30
- raise EntityExistsError('View already exists', name)
29
+ raise EntityExistsError("View already exists", name)
31
30
 
32
- view_record = db.View(
33
- name=name,
34
- company_id=ctx.company_id,
35
- query=query,
36
- project_id=project_id
37
- )
31
+ view_record = db.View(name=name, company_id=ctx.company_id, query=query, project_id=project_id)
38
32
  db.session.add(view_record)
39
33
  db.session.commit()
40
34
 
41
- def update(self, name, query, project_name):
42
- name = name.lower()
35
+ def update(self, name: str, query: str, project_name: str, strict_case: bool = False):
36
+ """Update the SQL query of an existing view in the specified project.
37
+
38
+ Args:
39
+ name (str): The name of the view to update.
40
+ query (str): The new SQL query for the view.
41
+ project_name (str): The name of the project containing the view.
42
+ strict_case (bool, optional): If True, the view name is case-sensitive. If False, the name comparison is case-insensitive. Defaults to False.
43
+
44
+ Raises:
45
+ EntityNotExistsError: If the view with the specified name does not exist in the given project.
46
+
47
+ Returns:
48
+ None
49
+ """
43
50
  project_record = get_project_record(project_name)
44
51
 
45
- rec = db.session.query(db.View).filter(
46
- func.lower(db.View.name) == name,
47
- db.View.company_id == ctx.company_id,
48
- db.View.project_id == project_record.id
49
- ).first()
52
+ q = db.session.query(db.View).filter(
53
+ db.View.company_id == ctx.company_id, db.View.project_id == project_record.id
54
+ )
55
+ if strict_case:
56
+ q = q.filter(db.View.name == name)
57
+ else:
58
+ q = q.filter(func.lower(db.View.name) == func.lower(name))
59
+
60
+ rec = q.first()
50
61
  if rec is None:
51
- raise EntityNotExistsError('View not found', name)
62
+ raise EntityNotExistsError("View not found", name)
52
63
  rec.query = query
53
64
  db.session.commit()
54
65
 
55
- def delete(self, name, project_name):
56
- name = name.lower()
66
+ def delete(self, name: str, project_name: str, strict_case: bool = False) -> None:
67
+ """Remove a view with the specified name from the given project.
68
+
69
+ Args:
70
+ name (str): The name of the view to remove.
71
+ project_name (str): The name of the project containing the view.
72
+ strict_case (bool, optional): If True, the view name is case-sensitive. Defaults to False.
73
+
74
+ Raises:
75
+ EntityNotExistsError: If the view does not exist.
76
+
77
+ Returns:
78
+ None
79
+ """
57
80
  project_record = get_project_record(project_name)
58
81
 
59
- rec = db.session.query(db.View).filter(
60
- func.lower(db.View.name) == name,
61
- db.View.company_id == ctx.company_id,
62
- db.View.project_id == project_record.id
63
- ).first()
64
- if rec is None:
65
- raise EntityNotExistsError('View not found', name)
66
- db.session.delete(rec)
82
+ query = db.session.query(db.View).filter(
83
+ db.View.company_id == ctx.company_id, db.View.project_id == project_record.id
84
+ )
85
+ if strict_case:
86
+ query = query.filter(db.View.name == name)
87
+ else:
88
+ query = query.filter(func.lower(db.View.name) == func.lower(name))
89
+
90
+ record = query.first()
91
+ if record is None:
92
+ raise EntityNotExistsError("View not found", name)
93
+ db.session.delete(record)
67
94
  db.session.commit()
68
95
 
69
- query_context_controller.drop_query_context('view', rec.id)
96
+ query_context_controller.drop_query_context("view", record.id)
70
97
 
71
98
  def list(self, project_name):
72
-
73
99
  project_names = {}
74
100
  for project in get_project_records():
75
101
  if project_name is not None and project.name != project_name:
@@ -77,49 +103,49 @@ class ViewController:
77
103
  project_names[project.id] = project.name
78
104
 
79
105
  query = db.session.query(db.View).filter(
80
- db.View.company_id == ctx.company_id,
81
- db.View.project_id.in_(list(project_names.keys()))
106
+ db.View.company_id == ctx.company_id, db.View.project_id.in_(list(project_names.keys()))
82
107
  )
83
108
 
84
109
  data = []
85
110
 
86
111
  for record in query:
87
-
88
- data.append({
89
- 'id': record.id,
90
- 'name': record.name,
91
- 'project': project_names[record.project_id],
92
- 'query': record.query,
93
- })
112
+ data.append(
113
+ {
114
+ "id": record.id,
115
+ "name": record.name,
116
+ "project": project_names[record.project_id],
117
+ "query": record.query,
118
+ }
119
+ )
94
120
 
95
121
  return data
96
122
 
97
123
  def _get_view_record_data(self, record):
98
- return {
99
- 'id': record.id,
100
- 'name': record.name,
101
- 'query': record.query
102
- }
124
+ return {"id": record.id, "name": record.name, "query": record.query}
103
125
 
104
126
  def get(self, id=None, name=None, project_name=None):
105
127
  project_record = get_project_record(project_name)
106
128
 
107
129
  if id is not None:
108
- records = db.session.query(db.View).filter_by(
109
- id=id,
110
- project_id=project_record.id,
111
- company_id=ctx.company_id
112
- ).all()
130
+ records = (
131
+ db.session.query(db.View)
132
+ .filter_by(id=id, project_id=project_record.id, company_id=ctx.company_id)
133
+ .all()
134
+ )
113
135
  elif name is not None:
114
- records = db.session.query(db.View).filter(
115
- func.lower(db.View.name) == name.lower(),
116
- db.View.project_id == project_record.id,
117
- db.View.company_id == ctx.company_id,
118
- ).all()
136
+ records = (
137
+ db.session.query(db.View)
138
+ .filter(
139
+ func.lower(db.View.name) == name.lower(),
140
+ db.View.project_id == project_record.id,
141
+ db.View.company_id == ctx.company_id,
142
+ )
143
+ .all()
144
+ )
119
145
  if len(records) == 0:
120
146
  if name is None:
121
- name = f'id={id}'
122
- raise EntityNotExistsError("Can't find view", f'{project_name}.{name}')
147
+ name = f"id={id}"
148
+ raise EntityNotExistsError("Can't find view", f"{project_name}.{name}")
123
149
  elif len(records) > 1:
124
150
  raise Exception(f"There are multiple views with name/id: {name}/{id}")
125
151
  record = records[0]