MindsDB 25.2.4.0__py3-none-any.whl → 25.3.2.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 (64) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +16 -1
  3. mindsdb/api/executor/command_executor.py +1 -1
  4. mindsdb/api/executor/datahub/datanodes/system_tables.py +6 -1
  5. mindsdb/api/executor/planner/query_planner.py +6 -2
  6. mindsdb/api/executor/sql_query/steps/prepare_steps.py +2 -1
  7. mindsdb/api/executor/sql_query/steps/union_step.py +21 -24
  8. mindsdb/api/http/gui.py +5 -4
  9. mindsdb/api/http/initialize.py +19 -19
  10. mindsdb/api/mongo/classes/query_sql.py +2 -1
  11. mindsdb/api/mongo/responders/aggregate.py +2 -2
  12. mindsdb/api/mongo/responders/coll_stats.py +3 -2
  13. mindsdb/api/mongo/responders/db_stats.py +2 -1
  14. mindsdb/api/mongo/responders/insert.py +4 -2
  15. mindsdb/api/mysql/mysql_proxy/classes/fake_mysql_proxy/fake_mysql_proxy.py +2 -1
  16. mindsdb/api/mysql/mysql_proxy/mysql_proxy.py +5 -4
  17. mindsdb/api/postgres/postgres_proxy/postgres_proxy.py +2 -4
  18. mindsdb/integrations/handlers/autosklearn_handler/autosklearn_handler.py +1 -1
  19. mindsdb/integrations/handlers/gmail_handler/connection_args.py +2 -2
  20. mindsdb/integrations/handlers/gmail_handler/gmail_handler.py +19 -66
  21. mindsdb/integrations/handlers/gmail_handler/requirements.txt +0 -1
  22. mindsdb/integrations/handlers/google_calendar_handler/connection_args.py +15 -0
  23. mindsdb/integrations/handlers/google_calendar_handler/google_calendar_handler.py +31 -41
  24. mindsdb/integrations/handlers/google_calendar_handler/requirements.txt +0 -2
  25. mindsdb/integrations/handlers/jira_handler/__init__.py +1 -0
  26. mindsdb/integrations/handlers/jira_handler/jira_handler.py +22 -80
  27. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +3 -3
  28. mindsdb/integrations/handlers/slack_handler/slack_handler.py +2 -1
  29. mindsdb/integrations/handlers/youtube_handler/youtube_handler.py +2 -38
  30. mindsdb/integrations/libs/api_handler_generator.py +583 -0
  31. mindsdb/integrations/libs/llm/utils.py +2 -1
  32. mindsdb/integrations/utilities/handlers/auth_utilities/google/google_user_oauth_utilities.py +29 -38
  33. mindsdb/integrations/utilities/pydantic_utils.py +208 -0
  34. mindsdb/integrations/utilities/rag/pipelines/rag.py +11 -4
  35. mindsdb/integrations/utilities/rag/retrievers/sql_retriever.py +800 -135
  36. mindsdb/integrations/utilities/rag/settings.py +390 -152
  37. mindsdb/integrations/utilities/sql_utils.py +2 -1
  38. mindsdb/interfaces/agents/agents_controller.py +11 -7
  39. mindsdb/interfaces/agents/mindsdb_chat_model.py +4 -2
  40. mindsdb/interfaces/chatbot/chatbot_controller.py +9 -8
  41. mindsdb/interfaces/database/database.py +2 -1
  42. mindsdb/interfaces/database/projects.py +28 -2
  43. mindsdb/interfaces/jobs/jobs_controller.py +4 -1
  44. mindsdb/interfaces/model/model_controller.py +5 -2
  45. mindsdb/interfaces/skills/retrieval_tool.py +128 -39
  46. mindsdb/interfaces/skills/skill_tool.py +7 -7
  47. mindsdb/interfaces/skills/skills_controller.py +8 -4
  48. mindsdb/interfaces/storage/db.py +14 -0
  49. mindsdb/interfaces/storage/json.py +59 -0
  50. mindsdb/interfaces/storage/model_fs.py +85 -3
  51. mindsdb/interfaces/triggers/triggers_controller.py +2 -1
  52. mindsdb/migrations/versions/2022-10-14_43c52d23845a_projects.py +17 -3
  53. mindsdb/migrations/versions/2025-02-14_4521dafe89ab_added_encrypted_content_to_json_storage.py +29 -0
  54. mindsdb/migrations/versions/2025-02-19_11347c213b36_added_metadata_to_projects.py +41 -0
  55. mindsdb/utilities/config.py +6 -2
  56. mindsdb/utilities/functions.py +11 -0
  57. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.2.0.dist-info}/METADATA +219 -222
  58. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.2.0.dist-info}/RECORD +61 -60
  59. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.2.0.dist-info}/WHEEL +1 -1
  60. mindsdb/integrations/handlers/gmail_handler/utils.py +0 -45
  61. mindsdb/integrations/handlers/jira_handler/jira_table.py +0 -172
  62. mindsdb/integrations/handlers/jira_handler/requirements.txt +0 -1
  63. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.2.0.dist-info}/LICENSE +0 -0
  64. {MindsDB-25.2.4.0.dist-info → mindsdb-25.3.2.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from mindsdb.utilities.functions import decrypt_json, encrypt_json
2
+ from mindsdb.utilities.config import config
1
3
  from mindsdb.interfaces.storage import db
2
4
  from mindsdb.interfaces.storage.fs import RESOURCE_GROUP
3
5
  from mindsdb.utilities.context import context as ctx
@@ -90,8 +92,65 @@ class JsonStorage:
90
92
  logger.error('cant delete records from JSON storage')
91
93
 
92
94
 
95
+ class EncryptedJsonStorage(JsonStorage):
96
+ def __init__(self, resource_group: str, resource_id: int):
97
+ super().__init__(resource_group, resource_id)
98
+ self.secret_key = config.get('secret_key', 'dummy-key')
99
+
100
+ def __setitem__(self, key: str, value: dict) -> None:
101
+ if isinstance(value, dict) is False:
102
+ raise TypeError(f"got {type(value)} instead of dict")
103
+
104
+ encrypted_value = encrypt_json(value, self.secret_key)
105
+
106
+ existing_record = self.get_record(key)
107
+ if existing_record is None:
108
+ record = db.JsonStorage(
109
+ name=key,
110
+ resource_group=self.resource_group,
111
+ resource_id=self.resource_id,
112
+ company_id=ctx.company_id,
113
+ encrypted_content=encrypted_value
114
+ )
115
+ db.session.add(record)
116
+ else:
117
+ existing_record.encrypted_content = encrypted_value
118
+ db.session.commit()
119
+
120
+ def set_bytes(self, key: str, encrypted_value: bytes):
121
+ existing_record = self.get_record(key)
122
+ if existing_record is None:
123
+ record = db.JsonStorage(
124
+ name=key,
125
+ resource_group=self.resource_group,
126
+ resource_id=self.resource_id,
127
+ company_id=ctx.company_id,
128
+ encrypted_content=encrypted_value
129
+ )
130
+ db.session.add(record)
131
+ else:
132
+ existing_record.encrypted_content = encrypted_value
133
+ db.session.commit()
134
+
135
+ def set_str(self, key: str, encrypted_value: str):
136
+ self.set_bytes(key, encrypted_value.encode())
137
+
138
+ def __getitem__(self, key: str) -> dict:
139
+ record = self.get_record(key)
140
+ if record is None:
141
+ return None
142
+ return decrypt_json(record.encrypted_content, self.secret_key)
143
+
144
+
93
145
  def get_json_storage(resource_id: int, resource_group: str = RESOURCE_GROUP.PREDICTOR):
94
146
  return JsonStorage(
95
147
  resource_group=resource_group,
96
148
  resource_id=resource_id,
97
149
  )
150
+
151
+
152
+ def get_encrypted_json_storage(resource_id: int, resource_group: str = RESOURCE_GROUP.PREDICTOR):
153
+ return EncryptedJsonStorage(
154
+ resource_group=resource_group,
155
+ resource_id=resource_id,
156
+ )
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import re
3
+ import json
3
4
  import io
4
5
  import zipfile
5
6
  from typing import Union
@@ -7,7 +8,10 @@ from typing import Union
7
8
  import mindsdb.interfaces.storage.db as db
8
9
 
9
10
  from .fs import RESOURCE_GROUP, FileStorageFactory, SERVICE_FILES_NAMES
10
- from .json import get_json_storage
11
+ from .json import get_json_storage, get_encrypted_json_storage
12
+
13
+
14
+ JSON_STORAGE_FILE = 'json_storage.json'
11
15
 
12
16
 
13
17
  class ModelStorage:
@@ -119,6 +123,13 @@ class ModelStorage:
119
123
  )
120
124
  return json_storage.set(name, data)
121
125
 
126
+ def encrypted_json_set(self, name: str, data: dict) -> None:
127
+ json_storage = get_encrypted_json_storage(
128
+ resource_id=self.predictor_id,
129
+ resource_group=RESOURCE_GROUP.PREDICTOR
130
+ )
131
+ return json_storage.set(name, data)
132
+
122
133
  def json_get(self, name):
123
134
  json_storage = get_json_storage(
124
135
  resource_id=self.predictor_id,
@@ -126,6 +137,13 @@ class ModelStorage:
126
137
  )
127
138
  return json_storage.get(name)
128
139
 
140
+ def encrypted_json_get(self, name: str) -> dict:
141
+ json_storage = get_encrypted_json_storage(
142
+ resource_id=self.predictor_id,
143
+ resource_group=RESOURCE_GROUP.PREDICTOR
144
+ )
145
+ return json_storage.get(name)
146
+
129
147
  def json_list(self):
130
148
  ...
131
149
 
@@ -237,6 +255,13 @@ class HandlerStorage:
237
255
  )
238
256
  return json_storage.set(name, content)
239
257
 
258
+ def encrypted_json_set(self, name: str, content: dict) -> None:
259
+ json_storage = get_encrypted_json_storage(
260
+ resource_id=self.integration_id,
261
+ resource_group=RESOURCE_GROUP.INTEGRATION
262
+ )
263
+ return json_storage.set(name, content)
264
+
240
265
  def json_get(self, name):
241
266
  json_storage = get_json_storage(
242
267
  resource_id=self.integration_id,
@@ -244,6 +269,13 @@ class HandlerStorage:
244
269
  )
245
270
  return json_storage.get(name)
246
271
 
272
+ def encrypted_json_get(self, name: str) -> dict:
273
+ json_storage = get_encrypted_json_storage(
274
+ resource_id=self.integration_id,
275
+ resource_group=RESOURCE_GROUP.INTEGRATION
276
+ )
277
+ return json_storage.get(name)
278
+
247
279
  def json_list(self):
248
280
  ...
249
281
 
@@ -251,8 +283,11 @@ class HandlerStorage:
251
283
  ...
252
284
 
253
285
  def export_files(self) -> bytes:
254
- if self.is_empty():
286
+ json_storage = self.export_json_storage()
287
+
288
+ if self.is_empty() and not json_storage:
255
289
  return None
290
+
256
291
  folder_path = self.folder_get('')
257
292
 
258
293
  zip_fd = io.BytesIO()
@@ -265,6 +300,11 @@ class HandlerStorage:
265
300
  abs_path = os.path.join(root, file_name)
266
301
  zipf.write(abs_path, os.path.relpath(abs_path, folder_path))
267
302
 
303
+ # If JSON storage is not empty, add it to the zip file.
304
+ if json_storage:
305
+ json_str = json.dumps(json_storage)
306
+ zipf.writestr(JSON_STORAGE_FILE, json_str)
307
+
268
308
  zip_fd.seek(0)
269
309
  return zip_fd.read()
270
310
 
@@ -277,6 +317,48 @@ class HandlerStorage:
277
317
  zip_fd.seek(0)
278
318
 
279
319
  with zipfile.ZipFile(zip_fd, 'r') as zip_ref:
280
- zip_ref.extractall(folder_path)
320
+ for name in zip_ref.namelist():
321
+ # If JSON storage file is in the zip file, import the content to the JSON storage.
322
+ # Thereafter, remove the file from the folder.
323
+ if name == JSON_STORAGE_FILE:
324
+ json_storage = zip_ref.read(JSON_STORAGE_FILE)
325
+ self.import_json_storage(json_storage)
326
+
327
+ else:
328
+ zip_ref.extract(name, folder_path)
281
329
 
282
330
  self.folder_sync('')
331
+
332
+ def export_json_storage(self) -> list[dict]:
333
+ json_storage = get_json_storage(
334
+ resource_id=self.integration_id,
335
+ resource_group=RESOURCE_GROUP.INTEGRATION
336
+ )
337
+
338
+ records = []
339
+ for record in json_storage.get_all_records():
340
+ record_dict = record.to_dict()
341
+ if record_dict.get('encrypted_content'):
342
+ record_dict['encrypted_content'] = record_dict['encrypted_content'].decode()
343
+ records.append(record_dict)
344
+
345
+ return records
346
+
347
+ def import_json_storage(self, records: bytes) -> None:
348
+ json_storage = get_json_storage(
349
+ resource_id=self.integration_id,
350
+ resource_group=RESOURCE_GROUP.INTEGRATION
351
+ )
352
+
353
+ encrypted_json_storage = get_encrypted_json_storage(
354
+ resource_id=self.integration_id,
355
+ resource_group=RESOURCE_GROUP.INTEGRATION
356
+ )
357
+
358
+ records = json.loads(records.decode())
359
+
360
+ for record in records:
361
+ if record['encrypted_content']:
362
+ encrypted_json_storage.set_str(record['name'], record['encrypted_content'])
363
+ else:
364
+ json_storage.set(record['name'], record['content'])
@@ -5,6 +5,7 @@ from mindsdb_sql_parser import parse_sql, ParsingException
5
5
  from mindsdb.interfaces.storage import db
6
6
  from mindsdb.interfaces.database.projects import ProjectController
7
7
  from mindsdb.utilities.context import context as ctx
8
+ from mindsdb.utilities.config import config
8
9
 
9
10
  from mindsdb.api.executor.controllers.session_controller import SessionController
10
11
 
@@ -16,7 +17,7 @@ class TriggersController:
16
17
  name = name.lower()
17
18
 
18
19
  if project_name is None:
19
- project_name = 'mindsdb'
20
+ project_name = config.get('default_project')
20
21
  project_controller = ProjectController()
21
22
  project = project_controller.get(name=project_name)
22
23
 
@@ -32,12 +32,26 @@ def upgrade():
32
32
  sa.UniqueConstraint('name', 'company_id', name='unique_project_name_company_id')
33
33
  )
34
34
 
35
+ project_table = sa.Table(
36
+ 'project',
37
+ sa.MetaData(),
38
+ sa.Column('id', sa.Integer()),
39
+ sa.Column('name', sa.String()),
40
+ sa.Column('company_id', sa.Integer()),
41
+ )
42
+
35
43
  conn = op.get_bind()
36
44
  session = sa.orm.Session(bind=conn)
37
45
 
38
- project_record = db.Project(name='mindsdb')
39
- session.add(project_record)
40
- session.commit()
46
+ conn.execute(
47
+ project_table.insert().values(
48
+ name='mindsdb'
49
+ )
50
+ )
51
+
52
+ project_record = conn.execute(
53
+ project_table.select().where(project_table.c.name == 'mindsdb')
54
+ ).fetchone()
41
55
 
42
56
  with op.batch_alter_table('predictor', schema=None) as batch_op:
43
57
  batch_op.add_column(sa.Column('project_id', sa.Integer()))
@@ -0,0 +1,29 @@
1
+ """added_encrypted_content_to_json_storage
2
+
3
+ Revision ID: 4521dafe89ab
4
+ Revises: 6ab9903fc59a
5
+ Create Date: 2025-02-14 12:05:13.102594
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ import mindsdb.interfaces.storage.db # noqa
11
+
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = '4521dafe89ab'
15
+ down_revision = '6ab9903fc59a'
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ with op.batch_alter_table('json_storage', schema=None) as batch_op:
22
+ batch_op.add_column(sa.Column('encrypted_content', sa.LargeBinary(), nullable=True))
23
+ batch_op.alter_column('resource_id', existing_type=sa.Integer(), type_=sa.BigInteger())
24
+
25
+
26
+ def downgrade():
27
+ with op.batch_alter_table('json_storage', schema=None) as batch_op:
28
+ batch_op.drop_column('encrypted_content')
29
+ batch_op.alter_column('resource_id', existing_type=sa.BigInteger(), type_=sa.Integer())
@@ -0,0 +1,41 @@
1
+ """added_metadata_to_projects
2
+
3
+ Revision ID: 11347c213b36
4
+ Revises: 4521dafe89ab
5
+ Create Date: 2025-02-19 18:46:24.014843
6
+
7
+ """
8
+ from alembic import op
9
+ from sqlalchemy.orm.attributes import flag_modified
10
+ import sqlalchemy as sa
11
+
12
+ import mindsdb.interfaces.storage.db as db
13
+ from mindsdb.utilities.config import config
14
+
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision = '11347c213b36'
18
+ down_revision = '4521dafe89ab'
19
+ branch_labels = None
20
+ depends_on = None
21
+
22
+
23
+ def upgrade():
24
+ with op.batch_alter_table('project', schema=None) as batch_op:
25
+ batch_op.add_column(sa.Column('metadata', sa.JSON(), nullable=True))
26
+
27
+ conn = op.get_bind()
28
+ session = sa.orm.Session(bind=conn)
29
+ session.commit()
30
+
31
+ project = session.query(db.Project).filter_by(name='mindsdb').first()
32
+ if project:
33
+ project.name = config.get('default_project')
34
+ project.metadata_ = {"is_default": True}
35
+ flag_modified(project, 'metadata_')
36
+ session.commit()
37
+
38
+
39
+ def downgrade():
40
+ with op.batch_alter_table('project', schema=None) as batch_op:
41
+ batch_op.drop_column('metadata')
@@ -217,7 +217,8 @@ class Config:
217
217
  },
218
218
  "tasks": {
219
219
  "disable": False
220
- }
220
+ },
221
+ "default_project": "mindsdb"
221
222
  }
222
223
  # endregion
223
224
 
@@ -353,6 +354,9 @@ class Config:
353
354
  if os.environ.get('MINDSDB_DB_CON', '') != '':
354
355
  self._env_config['storage_db'] = os.environ['MINDSDB_DB_CON']
355
356
 
357
+ if os.environ.get('MINDSDB_DEFAULT_PROJECT', '') != '':
358
+ self._env_config['default_project'] = os.environ['MINDSDB_DEFAULT_PROJECT'].lower()
359
+
356
360
  def parse_cmd_args(self) -> None:
357
361
  """Collect cmd args to self._cmd_args (accessable as self.cmd_args)
358
362
  """
@@ -495,7 +499,7 @@ class Config:
495
499
 
496
500
  for env_name in ('MINDSDB_HTTP_SERVER_TYPE', 'MINDSDB_DEFAULT_SERVER'):
497
501
  env_value = os.environ.get(env_name, '')
498
- if env_value.lower() not in ('waitress', 'flask', 'gunicorn'):
502
+ if env_value.lower() not in ('waitress', 'flask', 'gunicorn', ''):
499
503
  logger.warning(
500
504
  f"The value '{env_value}' of the environment variable {env_name} is not valid. "
501
505
  "It must be one of the following: 'waitress', 'flask', or 'gunicorn'."
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import base64
3
3
  import hashlib
4
+ import json
4
5
  import datetime
5
6
  import textwrap
6
7
  from functools import wraps
@@ -195,3 +196,13 @@ def decrypt(encripted: bytes, key: str) -> bytes:
195
196
 
196
197
  cipher = Fernet(fernet_key)
197
198
  return cipher.decrypt(encripted)
199
+
200
+
201
+ def encrypt_json(data: dict, key: str) -> bytes:
202
+ json_str = json.dumps(data)
203
+ return encrypt(json_str.encode(), key)
204
+
205
+
206
+ def decrypt_json(encrypted_data: bytes, key: str) -> dict:
207
+ decrypted = decrypt(encrypted_data, key)
208
+ return json.loads(decrypted)