MindsDB 25.7.3.0__py3-none-any.whl → 25.8.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.
- mindsdb/__about__.py +1 -1
- mindsdb/__main__.py +11 -1
- mindsdb/api/a2a/common/server/server.py +16 -6
- mindsdb/api/executor/command_executor.py +215 -150
- mindsdb/api/executor/datahub/datanodes/project_datanode.py +14 -3
- mindsdb/api/executor/planner/plan_join.py +3 -0
- mindsdb/api/executor/planner/plan_join_ts.py +117 -100
- mindsdb/api/executor/planner/query_planner.py +1 -0
- mindsdb/api/executor/sql_query/steps/apply_predictor_step.py +54 -85
- mindsdb/api/executor/sql_query/steps/fetch_dataframe.py +21 -24
- mindsdb/api/executor/sql_query/steps/fetch_dataframe_partition.py +9 -3
- mindsdb/api/executor/sql_query/steps/subselect_step.py +11 -8
- mindsdb/api/executor/utilities/mysql_to_duckdb_functions.py +264 -0
- mindsdb/api/executor/utilities/sql.py +30 -0
- mindsdb/api/http/initialize.py +18 -44
- mindsdb/api/http/namespaces/agents.py +23 -20
- mindsdb/api/http/namespaces/chatbots.py +83 -120
- mindsdb/api/http/namespaces/file.py +1 -1
- mindsdb/api/http/namespaces/jobs.py +38 -60
- mindsdb/api/http/namespaces/tree.py +69 -61
- mindsdb/api/http/namespaces/views.py +56 -72
- mindsdb/api/mcp/start.py +2 -0
- mindsdb/api/mysql/mysql_proxy/utilities/dump.py +3 -2
- mindsdb/integrations/handlers/autogluon_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/autosklearn_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/bigquery_handler/bigquery_handler.py +25 -5
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +3 -3
- mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
- mindsdb/integrations/handlers/flaml_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/gong_handler/__about__.py +2 -0
- mindsdb/integrations/handlers/gong_handler/__init__.py +30 -0
- mindsdb/integrations/handlers/gong_handler/connection_args.py +37 -0
- mindsdb/integrations/handlers/gong_handler/gong_handler.py +164 -0
- mindsdb/integrations/handlers/gong_handler/gong_tables.py +508 -0
- mindsdb/integrations/handlers/gong_handler/icon.svg +25 -0
- mindsdb/integrations/handlers/gong_handler/test_gong_handler.py +125 -0
- mindsdb/integrations/handlers/google_calendar_handler/google_calendar_tables.py +82 -73
- mindsdb/integrations/handlers/hubspot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/huggingface_handler/__init__.py +8 -12
- mindsdb/integrations/handlers/huggingface_handler/finetune.py +203 -223
- mindsdb/integrations/handlers/huggingface_handler/huggingface_handler.py +360 -383
- mindsdb/integrations/handlers/huggingface_handler/requirements.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +7 -7
- mindsdb/integrations/handlers/huggingface_handler/settings.py +25 -25
- mindsdb/integrations/handlers/langchain_handler/langchain_handler.py +83 -77
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +4 -4
- mindsdb/integrations/handlers/litellm_handler/litellm_handler.py +5 -2
- mindsdb/integrations/handlers/litellm_handler/settings.py +2 -1
- mindsdb/integrations/handlers/openai_handler/constants.py +11 -30
- mindsdb/integrations/handlers/openai_handler/helpers.py +27 -34
- mindsdb/integrations/handlers/openai_handler/openai_handler.py +14 -12
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +106 -90
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +41 -39
- mindsdb/integrations/handlers/salesforce_handler/constants.py +215 -0
- mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +141 -80
- mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +0 -1
- mindsdb/integrations/handlers/tpot_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/web_handler/urlcrawl_helpers.py +32 -17
- mindsdb/integrations/handlers/web_handler/web_handler.py +19 -22
- mindsdb/integrations/libs/llm/config.py +0 -14
- mindsdb/integrations/libs/llm/utils.py +0 -15
- mindsdb/integrations/libs/vectordatabase_handler.py +10 -1
- mindsdb/integrations/utilities/files/file_reader.py +5 -19
- mindsdb/integrations/utilities/handler_utils.py +32 -12
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +246 -149
- mindsdb/interfaces/agents/constants.py +0 -1
- mindsdb/interfaces/agents/langchain_agent.py +11 -6
- mindsdb/interfaces/data_catalog/data_catalog_loader.py +4 -4
- mindsdb/interfaces/database/database.py +38 -13
- mindsdb/interfaces/database/integrations.py +20 -5
- mindsdb/interfaces/database/projects.py +174 -23
- mindsdb/interfaces/database/views.py +86 -60
- mindsdb/interfaces/jobs/jobs_controller.py +103 -110
- mindsdb/interfaces/knowledge_base/controller.py +33 -6
- mindsdb/interfaces/knowledge_base/evaluate.py +2 -1
- mindsdb/interfaces/knowledge_base/executor.py +24 -0
- mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +6 -10
- mindsdb/interfaces/knowledge_base/preprocessing/text_splitter.py +73 -0
- mindsdb/interfaces/query_context/context_controller.py +111 -145
- mindsdb/interfaces/skills/skills_controller.py +18 -6
- mindsdb/interfaces/storage/db.py +40 -6
- mindsdb/interfaces/variables/variables_controller.py +8 -15
- mindsdb/utilities/config.py +5 -3
- mindsdb/utilities/fs.py +54 -17
- mindsdb/utilities/functions.py +72 -60
- mindsdb/utilities/log.py +38 -6
- mindsdb/utilities/ps.py +7 -7
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/METADATA +282 -268
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/RECORD +94 -92
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__about__.py +0 -9
- mindsdb/integrations/handlers/anyscale_endpoints_handler/__init__.py +0 -20
- mindsdb/integrations/handlers/anyscale_endpoints_handler/anyscale_endpoints_handler.py +0 -290
- mindsdb/integrations/handlers/anyscale_endpoints_handler/creation_args.py +0 -14
- mindsdb/integrations/handlers/anyscale_endpoints_handler/icon.svg +0 -4
- mindsdb/integrations/handlers/anyscale_endpoints_handler/requirements.txt +0 -2
- mindsdb/integrations/handlers/anyscale_endpoints_handler/settings.py +0 -51
- mindsdb/integrations/handlers/anyscale_endpoints_handler/tests/test_anyscale_endpoints_handler.py +0 -212
- /mindsdb/integrations/handlers/{anyscale_endpoints_handler/tests/__init__.py → gong_handler/requirements.txt} +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.7.3.0.dist-info → mindsdb-25.8.2.0.dist-info}/top_level.txt +0 -0
|
@@ -23,13 +23,16 @@ class SkillsController:
|
|
|
23
23
|
project_controller = ProjectController()
|
|
24
24
|
self.project_controller = project_controller
|
|
25
25
|
|
|
26
|
-
def get_skill(
|
|
26
|
+
def get_skill(
|
|
27
|
+
self, skill_name: str, project_name: str = default_project, strict_case: bool = False
|
|
28
|
+
) -> Optional[db.Skills]:
|
|
27
29
|
"""
|
|
28
30
|
Gets a skill by name. Skills are expected to have unique names.
|
|
29
31
|
|
|
30
32
|
Parameters:
|
|
31
33
|
skill_name (str): The name of the skill
|
|
32
34
|
project_name (str): The name of the containing project
|
|
35
|
+
strict_case (bool): If True, the skill name is case-sensitive. Defaults to False.
|
|
33
36
|
|
|
34
37
|
Returns:
|
|
35
38
|
skill (Optional[db.Skills]): The database skill object
|
|
@@ -39,11 +42,16 @@ class SkillsController:
|
|
|
39
42
|
"""
|
|
40
43
|
|
|
41
44
|
project = self.project_controller.get(name=project_name)
|
|
42
|
-
|
|
43
|
-
func.lower(db.Skills.name) == func.lower(skill_name),
|
|
45
|
+
query = db.Skills.query.filter(
|
|
44
46
|
db.Skills.project_id == project.id,
|
|
45
47
|
db.Skills.deleted_at == null(),
|
|
46
|
-
)
|
|
48
|
+
)
|
|
49
|
+
if strict_case:
|
|
50
|
+
query = query.filter(db.Skills.name == skill_name)
|
|
51
|
+
else:
|
|
52
|
+
query = query.filter(func.lower(db.Skills.name) == func.lower(skill_name))
|
|
53
|
+
|
|
54
|
+
return query.first()
|
|
47
55
|
|
|
48
56
|
def get_skills(self, project_name: Optional[str]) -> List[dict]:
|
|
49
57
|
"""
|
|
@@ -92,6 +100,9 @@ class SkillsController:
|
|
|
92
100
|
project_name = default_project
|
|
93
101
|
project = self.project_controller.get(name=project_name)
|
|
94
102
|
|
|
103
|
+
if not name.islower():
|
|
104
|
+
raise ValueError(f"The name must be in lower case: {name}")
|
|
105
|
+
|
|
95
106
|
skill = self.get_skill(name, project_name)
|
|
96
107
|
|
|
97
108
|
if skill is not None:
|
|
@@ -158,19 +169,20 @@ class SkillsController:
|
|
|
158
169
|
|
|
159
170
|
return existing_skill
|
|
160
171
|
|
|
161
|
-
def delete_skill(self, skill_name: str, project_name: str = default_project):
|
|
172
|
+
def delete_skill(self, skill_name: str, project_name: str = default_project, strict_case: bool = False):
|
|
162
173
|
"""
|
|
163
174
|
Deletes a skill by name.
|
|
164
175
|
|
|
165
176
|
Parameters:
|
|
166
177
|
skill_name (str): The name of the skill to delete
|
|
167
178
|
project_name (str): The name of the containing project
|
|
179
|
+
strict_case (bool): If true, then skill_name is case sensitive
|
|
168
180
|
|
|
169
181
|
Raises:
|
|
170
182
|
ValueError: If `project_name` does not exist or skill doesn't exist
|
|
171
183
|
"""
|
|
172
184
|
|
|
173
|
-
skill = self.get_skill(skill_name, project_name)
|
|
185
|
+
skill = self.get_skill(skill_name, project_name, strict_case)
|
|
174
186
|
if skill is None:
|
|
175
187
|
raise ValueError(f"Skill with name doesn't exist: {skill_name}")
|
|
176
188
|
if isinstance(skill.params, dict) and skill.params.get("is_demo") is True:
|
mindsdb/interfaces/storage/db.py
CHANGED
|
@@ -448,19 +448,53 @@ class Agents(Base):
|
|
|
448
448
|
deleted_at = Column(DateTime)
|
|
449
449
|
|
|
450
450
|
def as_dict(self) -> Dict:
|
|
451
|
-
|
|
451
|
+
skills = []
|
|
452
|
+
skills_extra_parameters = {}
|
|
453
|
+
for rel in self.skills_relationships:
|
|
454
|
+
skill = rel.skill
|
|
455
|
+
# Skip auto-generated SQL skills
|
|
456
|
+
if skill.params.get("description", "").startswith("Auto-generated SQL skill for agent"):
|
|
457
|
+
continue
|
|
458
|
+
skills.append(skill.as_dict())
|
|
459
|
+
skills_extra_parameters[skill.name] = rel.parameters or {}
|
|
460
|
+
|
|
461
|
+
params = self.params.copy()
|
|
462
|
+
|
|
463
|
+
agent_dict = {
|
|
452
464
|
"id": self.id,
|
|
453
465
|
"name": self.name,
|
|
454
466
|
"project_id": self.project_id,
|
|
455
|
-
"model_name": self.model_name,
|
|
456
|
-
"skills": [rel.skill.as_dict() for rel in self.skills_relationships],
|
|
457
|
-
"skills_extra_parameters": {rel.skill.name: (rel.parameters or {}) for rel in self.skills_relationships},
|
|
458
|
-
"provider": self.provider,
|
|
459
|
-
"params": self.params,
|
|
460
467
|
"updated_at": self.updated_at,
|
|
461
468
|
"created_at": self.created_at,
|
|
462
469
|
}
|
|
463
470
|
|
|
471
|
+
if self.model_name:
|
|
472
|
+
agent_dict["model_name"] = self.model_name
|
|
473
|
+
|
|
474
|
+
if self.provider:
|
|
475
|
+
agent_dict["provider"] = self.provider
|
|
476
|
+
|
|
477
|
+
# Since skills were depreciated, they are only used with Minds
|
|
478
|
+
# Minds expects the parameters to be provided as is without breaking them down
|
|
479
|
+
if skills:
|
|
480
|
+
agent_dict["skills"] = skills
|
|
481
|
+
agent_dict["skills_extra_parameters"] = skills_extra_parameters
|
|
482
|
+
agent_dict["params"] = params
|
|
483
|
+
else:
|
|
484
|
+
data = params.pop("data", {})
|
|
485
|
+
model = params.pop("model", {})
|
|
486
|
+
prompt_template = params.pop("prompt_template", None)
|
|
487
|
+
if data:
|
|
488
|
+
agent_dict["data"] = data
|
|
489
|
+
if model:
|
|
490
|
+
agent_dict["model"] = model
|
|
491
|
+
if prompt_template:
|
|
492
|
+
agent_dict["prompt_template"] = prompt_template
|
|
493
|
+
if params:
|
|
494
|
+
agent_dict["params"] = params
|
|
495
|
+
|
|
496
|
+
return agent_dict
|
|
497
|
+
|
|
464
498
|
|
|
465
499
|
class KnowledgeBase(Base):
|
|
466
500
|
__tablename__ = "knowledge_base"
|
|
@@ -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
|
-
|
|
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 ==
|
|
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
|
|
mindsdb/utilities/config.py
CHANGED
|
@@ -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["
|
|
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 = {}
|
|
@@ -599,6 +599,7 @@ class Config:
|
|
|
599
599
|
ml_task_queue_consumer=None,
|
|
600
600
|
agent=None,
|
|
601
601
|
project=None,
|
|
602
|
+
update_gui=False,
|
|
602
603
|
)
|
|
603
604
|
return
|
|
604
605
|
|
|
@@ -635,6 +636,7 @@ class Config:
|
|
|
635
636
|
help="MindsDB agent name to connect to",
|
|
636
637
|
)
|
|
637
638
|
parser.add_argument("--project-name", type=str, default=None, help="MindsDB project name")
|
|
639
|
+
parser.add_argument("--update-gui", action="store_true", default=False, help="Update GUI and exit")
|
|
638
640
|
|
|
639
641
|
self._cmd_args = parser.parse_args()
|
|
640
642
|
|
mindsdb/utilities/fs.py
CHANGED
|
@@ -12,6 +12,10 @@ from mindsdb.utilities import log
|
|
|
12
12
|
logger = log.getLogger(__name__)
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def get_tmp_dir() -> Path:
|
|
16
|
+
return Path(tempfile.gettempdir()).joinpath("mindsdb")
|
|
17
|
+
|
|
18
|
+
|
|
15
19
|
def _get_process_mark_id(unified: bool = False) -> str:
|
|
16
20
|
"""Creates a text that can be used to identify process+thread
|
|
17
21
|
Args:
|
|
@@ -26,7 +30,7 @@ def _get_process_mark_id(unified: bool = False) -> str:
|
|
|
26
30
|
|
|
27
31
|
|
|
28
32
|
def create_process_mark(folder="learn"):
|
|
29
|
-
p =
|
|
33
|
+
p = get_tmp_dir().joinpath(f"processes/{folder}/")
|
|
30
34
|
p.mkdir(parents=True, exist_ok=True)
|
|
31
35
|
mark = _get_process_mark_id()
|
|
32
36
|
p.joinpath(mark).touch()
|
|
@@ -43,7 +47,7 @@ def set_process_mark(folder: str, mark: str) -> None:
|
|
|
43
47
|
Returns:
|
|
44
48
|
str: process mark
|
|
45
49
|
"""
|
|
46
|
-
p =
|
|
50
|
+
p = get_tmp_dir().joinpath(f"processes/{folder}/")
|
|
47
51
|
p.mkdir(parents=True, exist_ok=True)
|
|
48
52
|
mark = f"{os.getpid()}-{threading.get_native_id()}-{mark}"
|
|
49
53
|
p.joinpath(mark).touch()
|
|
@@ -53,11 +57,7 @@ def set_process_mark(folder: str, mark: str) -> None:
|
|
|
53
57
|
def delete_process_mark(folder: str = "learn", mark: Optional[str] = None):
|
|
54
58
|
if mark is None:
|
|
55
59
|
mark = _get_process_mark_id()
|
|
56
|
-
p = (
|
|
57
|
-
Path(tempfile.gettempdir())
|
|
58
|
-
.joinpath(f"mindsdb/processes/{folder}/")
|
|
59
|
-
.joinpath(mark)
|
|
60
|
-
)
|
|
60
|
+
p = get_tmp_dir().joinpath(f"processes/{folder}/").joinpath(mark)
|
|
61
61
|
if p.exists():
|
|
62
62
|
p.unlink()
|
|
63
63
|
|
|
@@ -65,7 +65,7 @@ def delete_process_mark(folder: str = "learn", mark: Optional[str] = None):
|
|
|
65
65
|
def clean_process_marks():
|
|
66
66
|
"""delete all existing processes marks"""
|
|
67
67
|
logger.debug("Deleting PIDs..")
|
|
68
|
-
p =
|
|
68
|
+
p = get_tmp_dir().joinpath("processes/")
|
|
69
69
|
if p.exists() is False:
|
|
70
70
|
return
|
|
71
71
|
for path in p.iterdir():
|
|
@@ -81,7 +81,7 @@ def get_processes_dir_files_generator() -> Tuple[Path, int, int]:
|
|
|
81
81
|
Yields:
|
|
82
82
|
Tuple[Path, int, int]: file object, process is and thread id
|
|
83
83
|
"""
|
|
84
|
-
p =
|
|
84
|
+
p = get_tmp_dir().joinpath("processes/")
|
|
85
85
|
if p.exists() is False:
|
|
86
86
|
return
|
|
87
87
|
for path in p.iterdir():
|
|
@@ -112,9 +112,7 @@ def clean_unlinked_process_marks() -> List[int]:
|
|
|
112
112
|
try:
|
|
113
113
|
next(t for t in threads if t.id == thread_id)
|
|
114
114
|
except StopIteration:
|
|
115
|
-
logger.warning(
|
|
116
|
-
f"We have mark for process/thread {process_id}/{thread_id} but it does not exists"
|
|
117
|
-
)
|
|
115
|
+
logger.warning(f"We have mark for process/thread {process_id}/{thread_id} but it does not exists")
|
|
118
116
|
deleted_pids.append(process_id)
|
|
119
117
|
file.unlink()
|
|
120
118
|
|
|
@@ -124,14 +122,53 @@ def clean_unlinked_process_marks() -> List[int]:
|
|
|
124
122
|
continue
|
|
125
123
|
|
|
126
124
|
except psutil.NoSuchProcess:
|
|
127
|
-
logger.warning(
|
|
128
|
-
f"We have mark for process/thread {process_id}/{thread_id} but it does not exists"
|
|
129
|
-
)
|
|
125
|
+
logger.warning(f"We have mark for process/thread {process_id}/{thread_id} but it does not exists")
|
|
130
126
|
deleted_pids.append(process_id)
|
|
131
127
|
file.unlink()
|
|
132
128
|
return deleted_pids
|
|
133
129
|
|
|
134
130
|
|
|
131
|
+
def create_pid_file():
|
|
132
|
+
"""
|
|
133
|
+
Create mindsdb process pid file. Check if previous process exists and is running
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
p = get_tmp_dir()
|
|
137
|
+
p.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
pid_file = p.joinpath("pid")
|
|
139
|
+
if pid_file.exists():
|
|
140
|
+
# if process exists raise exception
|
|
141
|
+
pid = pid_file.read_text().strip()
|
|
142
|
+
try:
|
|
143
|
+
psutil.Process(int(pid))
|
|
144
|
+
raise Exception(f"Found PID file with existing process: {pid}")
|
|
145
|
+
except (psutil.Error, ValueError):
|
|
146
|
+
...
|
|
147
|
+
|
|
148
|
+
logger.warning(f"Found existing PID file ({pid}), removing")
|
|
149
|
+
pid_file.unlink()
|
|
150
|
+
|
|
151
|
+
pid_file.write_text(str(os.getpid()))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def delete_pid_file():
|
|
155
|
+
"""
|
|
156
|
+
Remove existing process pid file if it matches current process
|
|
157
|
+
"""
|
|
158
|
+
pid_file = get_tmp_dir().joinpath("pid")
|
|
159
|
+
|
|
160
|
+
if not pid_file.exists():
|
|
161
|
+
logger.warning("Mindsdb PID file does not exist")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
pid = pid_file.read_text().strip()
|
|
165
|
+
if pid != str(os.getpid()):
|
|
166
|
+
logger.warning("Process id in PID file doesn't match mindsdb pid")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
pid_file.unlink()
|
|
170
|
+
|
|
171
|
+
|
|
135
172
|
def __is_within_directory(directory, target):
|
|
136
173
|
abs_directory = os.path.abspath(directory)
|
|
137
174
|
abs_target = os.path.abspath(target)
|
|
@@ -141,8 +178,8 @@ def __is_within_directory(directory, target):
|
|
|
141
178
|
|
|
142
179
|
def safe_extract(tarfile, path=".", members=None, *, numeric_owner=False):
|
|
143
180
|
# for py >= 3.12
|
|
144
|
-
if hasattr(tarfile,
|
|
145
|
-
tarfile.extractall(path, members=members, numeric_owner=numeric_owner, filter=
|
|
181
|
+
if hasattr(tarfile, "data_filter"):
|
|
182
|
+
tarfile.extractall(path, members=members, numeric_owner=numeric_owner, filter="data")
|
|
146
183
|
return
|
|
147
184
|
|
|
148
185
|
# for py < 3.12
|
mindsdb/utilities/functions.py
CHANGED
|
@@ -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 ==
|
|
44
|
-
timestamp = datetime.datetime.
|
|
45
|
-
row[key] = timestamp.strftime(
|
|
46
|
-
elif t ==
|
|
47
|
-
timestamp = datetime.datetime.
|
|
48
|
-
row[key] = timestamp.strftime(
|
|
49
|
-
elif t ==
|
|
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
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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([
|
|
19
|
+
for p in psutil.process_iter(["pid"]):
|
|
20
20
|
try:
|
|
21
21
|
process = psutil.Process(p.pid)
|
|
22
|
-
connections = process.
|
|
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(
|
|
30
|
-
_conn = namedtuple(
|
|
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 ==
|
|
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.
|
|
69
|
+
cons = p.net_connections()
|
|
70
70
|
cons = [x.laddr.port for x in cons]
|
|
71
71
|
except Exception:
|
|
72
72
|
return []
|