MindsDB 25.7.3.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 (61) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/api/a2a/common/server/server.py +16 -6
  3. mindsdb/api/executor/command_executor.py +206 -135
  4. mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
  5. mindsdb/api/executor/planner/plan_join.py +3 -0
  6. mindsdb/api/executor/planner/plan_join_ts.py +117 -100
  7. mindsdb/api/executor/planner/query_planner.py +1 -0
  8. mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
  9. mindsdb/api/http/initialize.py +16 -43
  10. mindsdb/api/http/namespaces/agents.py +23 -20
  11. mindsdb/api/http/namespaces/chatbots.py +83 -120
  12. mindsdb/api/http/namespaces/file.py +1 -1
  13. mindsdb/api/http/namespaces/jobs.py +38 -60
  14. mindsdb/api/http/namespaces/tree.py +69 -61
  15. mindsdb/api/mcp/start.py +2 -0
  16. mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
  17. mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
  18. mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
  19. mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
  20. mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
  21. mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
  22. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
  23. mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
  24. mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -76
  25. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
  26. mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
  27. mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
  28. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
  29. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
  30. mindsdb/integrations/handlers/salesforce_handler/constants.py +208 -0
  31. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
  32. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
  33. mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
  34. mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
  35. mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
  36. mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
  37. mindsdb/integrations/utilities/handler_utils.py +32 -12
  38. mindsdb/interfaces/agents/agents_controller.py +167 -108
  39. mindsdb/interfaces/agents/langchain_agent.py +10 -3
  40. mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
  41. mindsdb/interfaces/database/database.py +38 -13
  42. mindsdb/interfaces/database/integrations.py +20 -5
  43. mindsdb/interfaces/database/projects.py +63 -16
  44. mindsdb/interfaces/database/views.py +86 -60
  45. mindsdb/interfaces/jobs/jobs_controller.py +103 -110
  46. mindsdb/interfaces/knowledge_base/controller.py +26 -5
  47. mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
  48. mindsdb/interfaces/knowledge_base/executor.py +24 -0
  49. mindsdb/interfaces/query_context/context_controller.py +100 -133
  50. mindsdb/interfaces/skills/skills_controller.py +18 -6
  51. mindsdb/interfaces/storage/db.py +40 -6
  52. mindsdb/interfaces/variables/variables_controller.py +8 -15
  53. mindsdb/utilities/config.py +3 -3
  54. mindsdb/utilities/functions.py +72 -60
  55. mindsdb/utilities/log.py +38 -6
  56. mindsdb/utilities/ps.py +7 -7
  57. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/METADATA +246 -247
  58. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/RECORD +61 -60
  59. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/WHEEL +0 -0
  60. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/licenses/LICENSE +0 -0
  61. {mindsdb-25.7.3.0.dist-info → mindsdb-25.7.4.0.dist-info}/top_level.txt +0 -0
@@ -15,13 +15,9 @@ ENV_VAR_PREFIX = "MDB_"
15
15
 
16
16
 
17
17
  class VariablesController:
18
-
19
18
  def __init__(self) -> None:
20
- self._storage = get_json_storage(
21
- resource_id=0,
22
- resource_group=RESOURCE_GROUP.SYSTEM
23
- )
24
- self._store_key = 'variables'
19
+ self._storage = get_json_storage(resource_id=0, resource_group=RESOURCE_GROUP.SYSTEM)
20
+ self._store_key = "variables"
25
21
  self._data = None
26
22
 
27
23
  def _get_data(self) -> dict:
@@ -54,7 +50,7 @@ class VariablesController:
54
50
  return os.environ[var_name]
55
51
 
56
52
  def _get_function(self, name: str) -> Callable:
57
- if name == 'from_env':
53
+ if name == "from_env":
58
54
  return self._from_env
59
55
  raise ValueError(f"Function {name} is not found")
60
56
 
@@ -81,16 +77,13 @@ class VariablesController:
81
77
 
82
78
  if isinstance(var, Variable):
83
79
  return self.get_value(var.value.lower())
80
+ if isinstance(var, Function):
81
+ fnc = self._get_function(var.op)
82
+ return fnc(*var.args)
84
83
  elif isinstance(var, dict):
85
- return {
86
- key: self.fill_parameters(value)
87
- for key, value in var.items()
88
- }
84
+ return {key: self.fill_parameters(value) for key, value in var.items()}
89
85
  elif isinstance(var, list):
90
- return [
91
- self.fill_parameters(value)
92
- for value in var
93
- ]
86
+ return [self.fill_parameters(value) for value in var]
94
87
  return var
95
88
 
96
89
 
@@ -318,7 +318,7 @@ class Config:
318
318
  self._env_config["logging"]["handlers"]["console"]["level"] = os.environ["MINDSDB_LOG_LEVEL"]
319
319
  self._env_config["logging"]["handlers"]["console"]["enabled"] = True
320
320
  if os.environ.get("MINDSDB_CONSOLE_LOG_LEVEL", "") != "":
321
- self._env_config["logging"]["handlers"]["console"]["level"] = os.environ["MINDSDB_LOG_LEVEL"]
321
+ self._env_config["logging"]["handlers"]["console"]["level"] = os.environ["MINDSDB_CONSOLE_LOG_LEVEL"]
322
322
  self._env_config["logging"]["handlers"]["console"]["enabled"] = True
323
323
  if os.environ.get("MINDSDB_FILE_LOG_LEVEL", "") != "":
324
324
  self._env_config["logging"]["handlers"]["file"]["level"] = os.environ["MINDSDB_FILE_LOG_LEVEL"]
@@ -459,8 +459,8 @@ class Config:
459
459
  """Merge multiple configs to one."""
460
460
  new_config = deepcopy(self._default_config)
461
461
  _merge_configs(new_config, self._user_config)
462
- _merge_configs(new_config, self._auto_config)
463
- _merge_configs(new_config, self._env_config)
462
+ _merge_configs(new_config, self._auto_config or {})
463
+ _merge_configs(new_config, self._env_config or {})
464
464
 
465
465
  # Apply command-line arguments for A2A
466
466
  a2a_config = {}
@@ -35,20 +35,19 @@ def get_handler_install_message(handler_name):
35
35
 
36
36
 
37
37
  def cast_row_types(row, field_types):
38
- '''
39
- '''
38
+ """ """
40
39
  keys = [x for x in row.keys() if x in field_types]
41
40
  for key in keys:
42
41
  t = field_types[key]
43
- if t == 'Timestamp' and isinstance(row[key], (int, float)):
44
- timestamp = datetime.datetime.utcfromtimestamp(row[key])
45
- row[key] = timestamp.strftime('%Y-%m-%d %H:%M:%S')
46
- elif t == 'Date' and isinstance(row[key], (int, float)):
47
- timestamp = datetime.datetime.utcfromtimestamp(row[key])
48
- row[key] = timestamp.strftime('%Y-%m-%d')
49
- elif t == 'Int' and isinstance(row[key], (int, float, str)):
42
+ if t == "Timestamp" and isinstance(row[key], (int, float)):
43
+ timestamp = datetime.datetime.fromtimestamp(row[key], datetime.timezone.utc)
44
+ row[key] = timestamp.strftime("%Y-%m-%d %H:%M:%S")
45
+ elif t == "Date" and isinstance(row[key], (int, float)):
46
+ timestamp = datetime.datetime.fromtimestamp(row[key], datetime.timezone.utc)
47
+ row[key] = timestamp.strftime("%Y-%m-%d")
48
+ elif t == "Int" and isinstance(row[key], (int, float, str)):
50
49
  try:
51
- logger.debug(f'cast {row[key]} to {int(row[key])}')
50
+ logger.debug(f"cast {row[key]} to {int(row[key])}")
52
51
  row[key] = int(row[key])
53
52
  except Exception:
54
53
  pass
@@ -67,13 +66,16 @@ def mark_process(name: str, custom_mark: str = None) -> Callable:
67
66
  return func(*args, **kwargs)
68
67
  finally:
69
68
  delete_process_mark(name, mark)
69
+
70
70
  return wrapper
71
+
71
72
  return mark_process_wrapper
72
73
 
73
74
 
74
75
  def init_lexer_parsers():
75
76
  from mindsdb_sql_parser.lexer import MindsDBLexer
76
77
  from mindsdb_sql_parser.parser import MindsDBParser
78
+
77
79
  return MindsDBLexer(), MindsDBParser()
78
80
 
79
81
 
@@ -86,62 +88,72 @@ def resolve_table_identifier(identifier: Identifier, default_database: str = Non
86
88
  elif parts_count == 2:
87
89
  return (parts[0], parts[1])
88
90
  else:
89
- raise Exception(f'Table identifier must contain max 2 parts: {parts}')
91
+ raise Exception(f"Table identifier must contain max 2 parts: {parts}")
90
92
 
91
93
 
92
94
  def resolve_model_identifier(identifier: Identifier) -> tuple:
93
- """ split model name to parts
94
-
95
- Identifier may be:
96
-
97
- Examples:
98
- >>> resolve_model_identifier(['a', 'b'])
99
- ('a', 'b', None)
100
-
101
- >>> resolve_model_identifier(['a', '1'])
102
- (None, 'a', 1)
103
-
104
- >>> resolve_model_identifier(['a'])
105
- (None, 'a', None)
106
-
107
- >>> resolve_model_identifier(['a', 'b', 'c'])
108
- (None, None, None) # not found
109
-
110
- Args:
111
- name (Identifier): Identifier parts
112
-
113
- Returns:
114
- tuple: (database_name, model_name, model_version)
115
95
  """
116
- parts = identifier.parts
117
- database_name = None
96
+ Splits a model identifier into its database, model name, and version components.
97
+
98
+ The identifier may contain one, two, or three parts.
99
+ The function supports both quoted and unquoted identifiers, and normalizes names to lowercase if unquoted.
100
+
101
+ Examples:
102
+ >>> resolve_model_identifier(Identifier(parts=['a', 'b']))
103
+ ('a', 'b', None)
104
+ >>> resolve_model_identifier(Identifier(parts=['a', '1']))
105
+ (None, 'a', 1)
106
+ >>> resolve_model_identifier(Identifier(parts=['a']))
107
+ (None, 'a', None)
108
+ >>> resolve_model_identifier(Identifier(parts=['a', 'b', 'c']))
109
+ (None, None, None) # not found
110
+
111
+ Args:
112
+ identifier (Identifier): The identifier object containing parts and is_quoted attributes.
113
+
114
+ Returns:
115
+ tuple: (database_name, model_name, model_version)
116
+ - database_name (str or None): The name of the database/project, or None if not specified.
117
+ - model_name (str or None): The name of the model, or None if not found.
118
+ - model_version (int or None): The model version as an integer, or None if not specified.
119
+ """
118
120
  model_name = None
119
- model_version = None
121
+ db_name = None
122
+ version = None
123
+ model_name_quoted = None
124
+ db_name_quoted = None
125
+
126
+ match identifier.parts, identifier.is_quoted:
127
+ case [model_name], [model_name_quoted]:
128
+ ...
129
+ case [model_name, str(version)], [model_name_quoted, _] if version.isdigit():
130
+ ...
131
+ case [model_name, int(version)], [model_name_quoted, _]:
132
+ ...
133
+ case [db_name, model_name], [db_name_quoted, model_name_quoted]:
134
+ ...
135
+ case [db_name, model_name, str(version)], [db_name_quoted, model_name_quoted, _] if version.isdigit():
136
+ ...
137
+ case [db_name, model_name, int(version)], [db_name_quoted, model_name_quoted, _]:
138
+ ...
139
+ case [db_name, model_name, str(version)], [db_name_quoted, model_name_quoted, _]:
140
+ # for back compatibility. May be delete?
141
+ return (None, None, None)
142
+ case _:
143
+ ... # may be raise ValueError?
144
+
145
+ if model_name_quoted is False:
146
+ model_name = model_name.lower()
147
+
148
+ if db_name_quoted is False:
149
+ db_name = db_name.lower()
150
+
151
+ if isinstance(version, int) or isinstance(version, str) and version.isdigit():
152
+ version = int(version)
153
+ else:
154
+ version = None
120
155
 
121
- parts_count = len(parts)
122
- if parts_count == 1:
123
- database_name = None
124
- model_name = parts[0]
125
- model_version = None
126
- elif parts_count == 2:
127
- if parts[-1].isdigit():
128
- database_name = None
129
- model_name = parts[0]
130
- model_version = int(parts[-1])
131
- else:
132
- database_name = parts[0]
133
- model_name = parts[1]
134
- model_version = None
135
- elif parts_count == 3:
136
- database_name = parts[0]
137
- model_name = parts[1]
138
- if parts[2].isdigit():
139
- model_version = int(parts[2])
140
- else:
141
- # not found
142
- return None, None, None
143
-
144
- return database_name, model_name, model_version
156
+ return db_name, model_name, version
145
157
 
146
158
 
147
159
  def encrypt(string: bytes, key: str) -> bytes:
mindsdb/utilities/log.py CHANGED
@@ -43,6 +43,13 @@ class ColorFormatter(logging.Formatter):
43
43
  return log_fmt.format(record)
44
44
 
45
45
 
46
+ FORMATTERS = {
47
+ "default": {"()": ColorFormatter},
48
+ "json": {"()": JsonFormatter},
49
+ "file": {"format": "%(asctime)s %(processName)15s %(levelname)-8s %(name)s: %(message)s"},
50
+ }
51
+
52
+
46
53
  def get_console_handler_config_level() -> int:
47
54
  console_handler_config = app_config["logging"]["handlers"]["console"]
48
55
  return getattr(logging, console_handler_config["level"])
@@ -60,7 +67,7 @@ def get_mindsdb_log_level() -> int:
60
67
  return min(console_handler_config_level, file_handler_config_level)
61
68
 
62
69
 
63
- def configure_logging(process_name: str = None):
70
+ def get_handlers_config(process_name: str) -> dict:
64
71
  handlers_config = {}
65
72
  console_handler_config = app_config["logging"]["handlers"]["console"]
66
73
  console_handler_config_level = getattr(logging, console_handler_config["level"])
@@ -89,16 +96,41 @@ def configure_logging(process_name: str = None):
89
96
  "maxBytes": file_handler_config["maxBytes"], # 0.5 Mb
90
97
  "backupCount": file_handler_config["backupCount"],
91
98
  }
99
+ return handlers_config
100
+
101
+
102
+ def get_uvicorn_logging_config(process_name: str) -> dict:
103
+ """Generate a logging configuration dictionary for Uvicorn using MindsDB's logging settings.
104
+
105
+ Args:
106
+ process_name (str): The name of the process to include in log file names and handlers.
107
+
108
+ Returns:
109
+ dict: A dictionary suitable for use with logging.config.dictConfig, configured for Uvicorn logging.
110
+ """
111
+ handlers_config = get_handlers_config(process_name)
112
+ mindsdb_log_level = get_mindsdb_log_level()
113
+ return {
114
+ "version": 1,
115
+ "formatters": FORMATTERS,
116
+ "handlers": handlers_config,
117
+ "loggers": {
118
+ "uvicorn": {
119
+ "handlers": list(handlers_config.keys()),
120
+ "level": mindsdb_log_level,
121
+ "propagate": False,
122
+ }
123
+ },
124
+ }
125
+
92
126
 
127
+ def configure_logging(process_name: str = None):
128
+ handlers_config = get_handlers_config(process_name)
93
129
  mindsdb_log_level = get_mindsdb_log_level()
94
130
 
95
131
  logging_config = dict(
96
132
  version=1,
97
- formatters={
98
- "default": {"()": ColorFormatter},
99
- "json": {"()": JsonFormatter},
100
- "file": {"format": "%(asctime)s %(processName)15s %(levelname)-8s %(name)s: %(message)s"},
101
- },
133
+ formatters=FORMATTERS,
102
134
  handlers=handlers_config,
103
135
  loggers={
104
136
  "": { # root logger
mindsdb/utilities/ps.py CHANGED
@@ -11,23 +11,23 @@ def get_child_pids(pid):
11
11
 
12
12
  def net_connections():
13
13
  """Cross-platform psutil.net_connections like interface"""
14
- if sys.platform.lower().startswith('linux'):
14
+ if sys.platform.lower().startswith("linux"):
15
15
  return psutil.net_connections()
16
16
 
17
17
  all_connections = []
18
18
  Pconn = None
19
- for p in psutil.process_iter(['pid']):
19
+ for p in psutil.process_iter(["pid"]):
20
20
  try:
21
21
  process = psutil.Process(p.pid)
22
- connections = process.connections()
22
+ connections = process.net_connections()
23
23
  if connections:
24
24
  for conn in connections:
25
25
  # Adding pid to the returned instance
26
26
  # for consistency with psutil.net_connections()
27
27
  if Pconn is None:
28
28
  fields = list(conn._fields)
29
- fields.append('pid')
30
- _conn = namedtuple('Pconn', fields)
29
+ fields.append("pid")
30
+ _conn = namedtuple("Pconn", fields)
31
31
  for attr in conn._fields:
32
32
  setattr(_conn, attr, getattr(conn, attr))
33
33
  _conn.pid = p.pid
@@ -43,7 +43,7 @@ def is_port_in_use(port_num):
43
43
  parent_process = psutil.Process()
44
44
  child_pids = [x.pid for x in parent_process.children(recursive=True)]
45
45
  conns = net_connections()
46
- portsinuse = [x.laddr[1] for x in conns if x.pid in child_pids and x.status == 'LISTEN']
46
+ portsinuse = [x.laddr[1] for x in conns if x.pid in child_pids and x.status == "LISTEN"]
47
47
  portsinuse.sort()
48
48
  return int(port_num) in portsinuse
49
49
 
@@ -66,7 +66,7 @@ def wait_port(port_num, timeout):
66
66
  def get_listen_ports(pid):
67
67
  try:
68
68
  p = psutil.Process(pid)
69
- cons = p.connections()
69
+ cons = p.net_connections()
70
70
  cons = [x.laddr.port for x in cons]
71
71
  except Exception:
72
72
  return []