MindsDB 25.3.3.0__py3-none-any.whl → 25.3.4.1__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 +2 -2
- mindsdb/api/executor/datahub/datanodes/information_schema_datanode.py +2 -6
- mindsdb/api/executor/datahub/datanodes/mindsdb_tables.py +1 -1
- mindsdb/api/http/namespaces/agents.py +9 -5
- mindsdb/api/http/namespaces/chatbots.py +6 -5
- mindsdb/api/http/namespaces/databases.py +5 -6
- mindsdb/api/http/namespaces/skills.py +5 -4
- mindsdb/api/http/namespaces/views.py +6 -7
- mindsdb/integrations/handlers/chromadb_handler/chromadb_handler.py +23 -2
- mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +164 -0
- mindsdb/integrations/handlers/confluence_handler/confluence_handler.py +54 -59
- mindsdb/integrations/handlers/confluence_handler/confluence_tables.py +753 -0
- mindsdb/integrations/handlers/confluence_handler/connection_args.py +8 -8
- mindsdb/integrations/handlers/dummy_data_handler/dummy_data_handler.py +16 -6
- mindsdb/integrations/handlers/file_handler/tests/test_file_handler.py +64 -83
- mindsdb/integrations/handlers/huggingface_handler/requirements.txt +5 -4
- mindsdb/integrations/handlers/huggingface_handler/requirements_cpu.txt +5 -5
- mindsdb/integrations/handlers/langchain_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/lightwood_handler/requirements.txt +3 -3
- mindsdb/integrations/handlers/litellm_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
- mindsdb/integrations/handlers/ms_one_drive_handler/ms_graph_api_one_drive_client.py +1 -1
- mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -0
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +114 -70
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +431 -0
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +2 -0
- mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +18 -4
- mindsdb/integrations/handlers/ray_serve_handler/ray_serve_handler.py +18 -16
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +26 -1
- mindsdb/integrations/libs/vectordatabase_handler.py +2 -2
- mindsdb/integrations/utilities/files/file_reader.py +3 -3
- mindsdb/integrations/utilities/handlers/api_utilities/microsoft/ms_graph_api_utilities.py +36 -2
- mindsdb/integrations/utilities/rag/settings.py +1 -0
- mindsdb/interfaces/chatbot/chatbot_controller.py +6 -4
- mindsdb/interfaces/jobs/jobs_controller.py +1 -4
- mindsdb/interfaces/knowledge_base/controller.py +9 -28
- mindsdb/interfaces/knowledge_base/preprocessing/document_preprocessor.py +1 -1
- mindsdb/interfaces/skills/skills_controller.py +8 -7
- {mindsdb-25.3.3.0.dist-info → mindsdb-25.3.4.1.dist-info}/METADATA +237 -237
- {mindsdb-25.3.3.0.dist-info → mindsdb-25.3.4.1.dist-info}/RECORD +43 -41
- {mindsdb-25.3.3.0.dist-info → mindsdb-25.3.4.1.dist-info}/WHEEL +1 -1
- mindsdb/integrations/handlers/confluence_handler/confluence_table.py +0 -193
- mindsdb/integrations/handlers/confluence_handler/requirements.txt +0 -1
- {mindsdb-25.3.3.0.dist-info → mindsdb-25.3.4.1.dist-info/licenses}/LICENSE +0 -0
- {mindsdb-25.3.3.0.dist-info → mindsdb-25.3.4.1.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
__title__ = 'MindsDB'
|
|
2
2
|
__package_name__ = 'mindsdb'
|
|
3
|
-
__version__ = '25.3.
|
|
3
|
+
__version__ = '25.3.4.1'
|
|
4
4
|
__description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
|
|
5
5
|
__email__ = "jorge@mindsdb.com"
|
|
6
6
|
__author__ = 'MindsDB Inc'
|
|
7
7
|
__github__ = 'https://github.com/mindsdb/mindsdb'
|
|
8
8
|
__pypi__ = 'https://pypi.org/project/mindsdb/'
|
|
9
|
-
__license__ = '
|
|
9
|
+
__license__ = 'Elastic License 2.0'
|
|
10
10
|
__copyright__ = 'Copyright(c) 2018 MindsDB, Inc'
|
|
@@ -3,12 +3,8 @@ import pandas as pd
|
|
|
3
3
|
from mindsdb_sql_parser.ast.base import ASTNode
|
|
4
4
|
|
|
5
5
|
from mindsdb.api.executor.datahub.datanodes.datanode import DataNode
|
|
6
|
-
from mindsdb.api.executor.datahub.datanodes.integration_datanode import
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
from mindsdb.api.executor.datahub.datanodes.project_datanode import (
|
|
10
|
-
ProjectDataNode,
|
|
11
|
-
)
|
|
6
|
+
from mindsdb.api.executor.datahub.datanodes.integration_datanode import IntegrationDataNode
|
|
7
|
+
from mindsdb.api.executor.datahub.datanodes.project_datanode import ProjectDataNode
|
|
12
8
|
from mindsdb.api.executor import exceptions as exc
|
|
13
9
|
from mindsdb.api.executor.utilities.sql import query_df
|
|
14
10
|
from mindsdb.api.executor.utilities.sql import get_query_tables
|
|
@@ -310,7 +310,7 @@ class ChatbotsTable(MdbTable):
|
|
|
310
310
|
):
|
|
311
311
|
project_name = query.where.args[1].value
|
|
312
312
|
|
|
313
|
-
chatbot_data = chatbot_controller.get_chatbots(project_name)
|
|
313
|
+
chatbot_data = chatbot_controller.get_chatbots(project_name=project_name)
|
|
314
314
|
|
|
315
315
|
columns = cls.columns
|
|
316
316
|
columns_lower = [col.lower() for col in columns]
|
|
@@ -47,7 +47,7 @@ def create_agent(project_name, name, agent):
|
|
|
47
47
|
|
|
48
48
|
try:
|
|
49
49
|
existing_agent = agents_controller.get_agent(name, project_name=project_name)
|
|
50
|
-
except ValueError:
|
|
50
|
+
except (ValueError, EntityNotExistsError):
|
|
51
51
|
# Project must exist.
|
|
52
52
|
return http_error(
|
|
53
53
|
HTTPStatus.NOT_FOUND,
|
|
@@ -141,7 +141,7 @@ class AgentResource(Resource):
|
|
|
141
141
|
f'Agent with name {agent_name} does not exist'
|
|
142
142
|
)
|
|
143
143
|
return existing_agent.as_dict()
|
|
144
|
-
except ValueError:
|
|
144
|
+
except (ValueError, EntityNotExistsError):
|
|
145
145
|
# Project needs to exist.
|
|
146
146
|
return http_error(
|
|
147
147
|
HTTPStatus.NOT_FOUND,
|
|
@@ -173,7 +173,11 @@ class AgentResource(Resource):
|
|
|
173
173
|
f'Project with name {project_name} does not exist'
|
|
174
174
|
)
|
|
175
175
|
if existing_agent_record is None:
|
|
176
|
-
|
|
176
|
+
return http_error(
|
|
177
|
+
HTTPStatus.BAD_REQUEST,
|
|
178
|
+
'Creation is not allowed',
|
|
179
|
+
'Creation of an agent using the PUT method is not allowed.'
|
|
180
|
+
)
|
|
177
181
|
|
|
178
182
|
agent = request.json['agent']
|
|
179
183
|
name = agent.get('name', None)
|
|
@@ -272,7 +276,7 @@ class AgentResource(Resource):
|
|
|
272
276
|
'Agent not found',
|
|
273
277
|
f'Agent with name {agent_name} does not exist'
|
|
274
278
|
)
|
|
275
|
-
except ValueError:
|
|
279
|
+
except (ValueError, EntityNotExistsError):
|
|
276
280
|
# Project needs to exist.
|
|
277
281
|
return http_error(
|
|
278
282
|
HTTPStatus.NOT_FOUND,
|
|
@@ -435,7 +439,7 @@ class AgentCompletions(Resource):
|
|
|
435
439
|
'Agent not found',
|
|
436
440
|
f'Agent with name {agent_name} does not exist'
|
|
437
441
|
)
|
|
438
|
-
except ValueError:
|
|
442
|
+
except (ValueError, EntityNotExistsError):
|
|
439
443
|
# Project needs to exist.
|
|
440
444
|
return http_error(
|
|
441
445
|
HTTPStatus.NOT_FOUND,
|
|
@@ -11,6 +11,7 @@ from mindsdb.metrics.metrics import api_endpoint_metrics
|
|
|
11
11
|
from mindsdb.interfaces.chatbot.chatbot_controller import ChatBotController
|
|
12
12
|
from mindsdb.interfaces.model.functions import PredictorRecordNotFound
|
|
13
13
|
from mindsdb.interfaces.storage.db import Predictor
|
|
14
|
+
from mindsdb.utilities.exception import EntityNotExistsError
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def create_chatbot(project_name, name, chatbot):
|
|
@@ -59,7 +60,7 @@ def create_chatbot(project_name, name, chatbot):
|
|
|
59
60
|
|
|
60
61
|
try:
|
|
61
62
|
existing_chatbot = chatbot_controller.get_chatbot(name, project_name=project_name)
|
|
62
|
-
except
|
|
63
|
+
except EntityNotExistsError:
|
|
63
64
|
# Project must exist.
|
|
64
65
|
return http_error(
|
|
65
66
|
HTTPStatus.NOT_FOUND,
|
|
@@ -152,7 +153,7 @@ class ChatBotsResource(Resource):
|
|
|
152
153
|
chatbot_controller = ChatBotController()
|
|
153
154
|
try:
|
|
154
155
|
all_bots = chatbot_controller.get_chatbots(project_name)
|
|
155
|
-
except ValueError:
|
|
156
|
+
except (ValueError, EntityNotExistsError):
|
|
156
157
|
# Project needs to exist.
|
|
157
158
|
return http_error(
|
|
158
159
|
HTTPStatus.NOT_FOUND,
|
|
@@ -197,7 +198,7 @@ class ChatBotResource(Resource):
|
|
|
197
198
|
f'Chatbot with name {chatbot_name} does not exist'
|
|
198
199
|
)
|
|
199
200
|
return existing_chatbot
|
|
200
|
-
except ValueError:
|
|
201
|
+
except (ValueError, EntityNotExistsError):
|
|
201
202
|
# Project needs to exist.
|
|
202
203
|
return http_error(
|
|
203
204
|
HTTPStatus.NOT_FOUND,
|
|
@@ -221,7 +222,7 @@ class ChatBotResource(Resource):
|
|
|
221
222
|
|
|
222
223
|
try:
|
|
223
224
|
existing_chatbot = chatbot_controller.get_chatbot(chatbot_name, project_name=project_name)
|
|
224
|
-
except
|
|
225
|
+
except EntityNotExistsError:
|
|
225
226
|
# Project needs to exist.
|
|
226
227
|
return http_error(
|
|
227
228
|
HTTPStatus.NOT_FOUND,
|
|
@@ -306,7 +307,7 @@ class ChatBotResource(Resource):
|
|
|
306
307
|
'Chatbot not found',
|
|
307
308
|
f'Chatbot with name {chatbot_name} does not exist'
|
|
308
309
|
)
|
|
309
|
-
except
|
|
310
|
+
except EntityNotExistsError:
|
|
310
311
|
# Project needs to exist.
|
|
311
312
|
return http_error(
|
|
312
313
|
HTTPStatus.NOT_FOUND,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
from http import HTTPStatus
|
|
2
|
-
import tempfile
|
|
3
1
|
import time
|
|
2
|
+
import shutil
|
|
3
|
+
import tempfile
|
|
4
|
+
from http import HTTPStatus
|
|
4
5
|
from typing import Dict
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
import shutil
|
|
7
|
-
from sqlalchemy.exc import NoResultFound
|
|
8
7
|
|
|
9
8
|
from flask import request
|
|
10
9
|
from flask_restx import Resource
|
|
@@ -337,7 +336,7 @@ class TablesList(Resource):
|
|
|
337
336
|
HTTPStatus.BAD_REQUEST, 'Error',
|
|
338
337
|
error_message
|
|
339
338
|
)
|
|
340
|
-
except
|
|
339
|
+
except EntityNotExistsError:
|
|
341
340
|
# Only support creating tables from integrations.
|
|
342
341
|
pass
|
|
343
342
|
|
|
@@ -419,7 +418,7 @@ class TableResource(Resource):
|
|
|
419
418
|
+ f'If you want to delete a model or view, use the projects/{database_name}/models/{table_name} or ' \
|
|
420
419
|
+ f'projects/{database_name}/views/{table_name} endpoints instead.'
|
|
421
420
|
return http_error(HTTPStatus.BAD_REQUEST, 'Error', error_message)
|
|
422
|
-
except
|
|
421
|
+
except EntityNotExistsError:
|
|
423
422
|
# Only support dropping tables from integrations.
|
|
424
423
|
pass
|
|
425
424
|
|
|
@@ -7,6 +7,7 @@ from mindsdb.metrics.metrics import api_endpoint_metrics
|
|
|
7
7
|
from mindsdb.api.http.namespaces.configs.projects import ns_conf
|
|
8
8
|
from mindsdb.api.http.utils import http_error
|
|
9
9
|
from mindsdb.interfaces.skills.skills_controller import SkillsController
|
|
10
|
+
from mindsdb.utilities.exception import EntityNotExistsError
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
def create_skill(project_name, skill):
|
|
@@ -52,7 +53,7 @@ class SkillsResource(Resource):
|
|
|
52
53
|
skills_controller = SkillsController()
|
|
53
54
|
try:
|
|
54
55
|
all_skills = skills_controller.get_skills(project_name)
|
|
55
|
-
except
|
|
56
|
+
except EntityNotExistsError:
|
|
56
57
|
# Project needs to exist.
|
|
57
58
|
return http_error(
|
|
58
59
|
HTTPStatus.NOT_FOUND,
|
|
@@ -88,7 +89,7 @@ class SkillResource(Resource):
|
|
|
88
89
|
skills_controller = SkillsController()
|
|
89
90
|
try:
|
|
90
91
|
existing_skill = skills_controller.get_skill(skill_name, project_name)
|
|
91
|
-
except
|
|
92
|
+
except EntityNotExistsError:
|
|
92
93
|
# Project needs to exist
|
|
93
94
|
return http_error(
|
|
94
95
|
HTTPStatus.NOT_FOUND,
|
|
@@ -120,7 +121,7 @@ class SkillResource(Resource):
|
|
|
120
121
|
|
|
121
122
|
try:
|
|
122
123
|
existing_skill = skills_controller.get_skill(skill_name, project_name)
|
|
123
|
-
except
|
|
124
|
+
except EntityNotExistsError:
|
|
124
125
|
# Project needs to exist
|
|
125
126
|
return http_error(
|
|
126
127
|
HTTPStatus.NOT_FOUND,
|
|
@@ -152,7 +153,7 @@ class SkillResource(Resource):
|
|
|
152
153
|
skills_controller = SkillsController()
|
|
153
154
|
try:
|
|
154
155
|
existing_skill = skills_controller.get_skill(skill_name, project_name)
|
|
155
|
-
except
|
|
156
|
+
except EntityNotExistsError:
|
|
156
157
|
# Project needs to exist
|
|
157
158
|
return http_error(
|
|
158
159
|
HTTPStatus.NOT_FOUND,
|
|
@@ -2,13 +2,12 @@ from http import HTTPStatus
|
|
|
2
2
|
|
|
3
3
|
from flask import request
|
|
4
4
|
from flask_restx import Resource
|
|
5
|
-
from sqlalchemy.exc import NoResultFound
|
|
6
|
-
|
|
7
5
|
|
|
8
6
|
from mindsdb.api.http.utils import http_error
|
|
9
7
|
from mindsdb.api.http.namespaces.configs.projects import ns_conf
|
|
10
8
|
from mindsdb.api.executor.controllers.session_controller import SessionController
|
|
11
9
|
from mindsdb.metrics.metrics import api_endpoint_metrics
|
|
10
|
+
from mindsdb.utilities.exception import EntityNotExistsError
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@ns_conf.route('/<project_name>/views')
|
|
@@ -20,7 +19,7 @@ class ViewsList(Resource):
|
|
|
20
19
|
session = SessionController()
|
|
21
20
|
try:
|
|
22
21
|
project = session.database_controller.get_project(project_name)
|
|
23
|
-
except
|
|
22
|
+
except EntityNotExistsError:
|
|
24
23
|
return http_error(
|
|
25
24
|
HTTPStatus.NOT_FOUND,
|
|
26
25
|
'Project not found',
|
|
@@ -55,7 +54,7 @@ class ViewsList(Resource):
|
|
|
55
54
|
|
|
56
55
|
try:
|
|
57
56
|
project = session.database_controller.get_project(project_name)
|
|
58
|
-
except
|
|
57
|
+
except EntityNotExistsError:
|
|
59
58
|
return http_error(HTTPStatus.NOT_FOUND, 'Not found', f'Project name {project_name} does not exist')
|
|
60
59
|
|
|
61
60
|
if project.get_view(name) is not None:
|
|
@@ -82,7 +81,7 @@ class ViewResource(Resource):
|
|
|
82
81
|
session = SessionController()
|
|
83
82
|
try:
|
|
84
83
|
project = session.database_controller.get_project(project_name)
|
|
85
|
-
except
|
|
84
|
+
except EntityNotExistsError:
|
|
86
85
|
return http_error(HTTPStatus.NOT_FOUND, 'Project not found', f'Project name {project_name} does not exist')
|
|
87
86
|
|
|
88
87
|
view = project.get_view(view_name)
|
|
@@ -106,7 +105,7 @@ class ViewResource(Resource):
|
|
|
106
105
|
session = SessionController()
|
|
107
106
|
try:
|
|
108
107
|
project = session.database_controller.get_project(project_name)
|
|
109
|
-
except
|
|
108
|
+
except EntityNotExistsError:
|
|
110
109
|
return http_error(HTTPStatus.NOT_FOUND, 'Project not found', f'Project name {project_name} does not exist')
|
|
111
110
|
|
|
112
111
|
existing_view = project.get_view(view_name)
|
|
@@ -143,7 +142,7 @@ class ViewResource(Resource):
|
|
|
143
142
|
session = SessionController()
|
|
144
143
|
try:
|
|
145
144
|
project = session.database_controller.get_project(project_name)
|
|
146
|
-
except
|
|
145
|
+
except EntityNotExistsError:
|
|
147
146
|
return http_error(HTTPStatus.NOT_FOUND, 'Project not found', f'Project name {project_name} does not exist')
|
|
148
147
|
|
|
149
148
|
if project.get_view(view_name) is None:
|
|
@@ -210,6 +210,7 @@ class ChromaDBHandler(VectorStoreHandler):
|
|
|
210
210
|
chroma_db_conditions = []
|
|
211
211
|
for condition in metadata_conditions:
|
|
212
212
|
metadata_key = condition.column.split(".")[-1]
|
|
213
|
+
|
|
213
214
|
chroma_db_conditions.append(
|
|
214
215
|
{
|
|
215
216
|
metadata_key: {
|
|
@@ -310,9 +311,29 @@ class ChromaDBHandler(VectorStoreHandler):
|
|
|
310
311
|
payload = {column: payload[column] for column in columns}
|
|
311
312
|
|
|
312
313
|
# always include distance
|
|
314
|
+
distance_filter = None
|
|
315
|
+
distance_col = TableField.DISTANCE.value
|
|
313
316
|
if distances is not None:
|
|
314
|
-
payload[
|
|
315
|
-
|
|
317
|
+
payload[distance_col] = distances
|
|
318
|
+
|
|
319
|
+
for cond in conditions:
|
|
320
|
+
if cond.column == distance_col:
|
|
321
|
+
distance_filter = cond
|
|
322
|
+
break
|
|
323
|
+
|
|
324
|
+
df = pd.DataFrame(payload)
|
|
325
|
+
if distance_filter is not None:
|
|
326
|
+
op_map = {
|
|
327
|
+
'<': '__lt__',
|
|
328
|
+
'<=': '__le__',
|
|
329
|
+
'>': '__gt__',
|
|
330
|
+
'>=': '__ge__',
|
|
331
|
+
'=': '__eq__',
|
|
332
|
+
}
|
|
333
|
+
op = op_map.get(distance_filter.op.value)
|
|
334
|
+
if op:
|
|
335
|
+
df = df[getattr(df[distance_col], op)(distance_filter.value)]
|
|
336
|
+
return df
|
|
316
337
|
|
|
317
338
|
def _dataframe_metadata_to_chroma_metadata(self, metadata: Union[Dict[str, str], str]) -> Optional[Dict[str, str]]:
|
|
318
339
|
"""Convert DataFrame metadata to ChromaDB compatible metadata format"""
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfluenceAPIClient:
|
|
7
|
+
def __init__(self, url: str, username: str, password: str):
|
|
8
|
+
self.url = url
|
|
9
|
+
self.username = username
|
|
10
|
+
self.password = password
|
|
11
|
+
self.session = requests.Session()
|
|
12
|
+
self.session.auth = (self.username, self.password)
|
|
13
|
+
self.session.headers.update({"Accept": "application/json"})
|
|
14
|
+
|
|
15
|
+
def get_spaces(
|
|
16
|
+
self,
|
|
17
|
+
ids: List[int] = None,
|
|
18
|
+
keys: List[str] = None,
|
|
19
|
+
space_type: str = None,
|
|
20
|
+
status: str = None,
|
|
21
|
+
sort_condition: str = None,
|
|
22
|
+
limit: int = None,
|
|
23
|
+
):
|
|
24
|
+
url = f"{self.url}/wiki/api/v2/spaces"
|
|
25
|
+
params = {
|
|
26
|
+
"description-format": "view",
|
|
27
|
+
}
|
|
28
|
+
if ids:
|
|
29
|
+
params["ids"] = ids
|
|
30
|
+
if keys:
|
|
31
|
+
params["keys"] = keys
|
|
32
|
+
if space_type:
|
|
33
|
+
params["type"] = space_type
|
|
34
|
+
if status:
|
|
35
|
+
params["status"] = status
|
|
36
|
+
if sort_condition:
|
|
37
|
+
params["sort"] = sort_condition
|
|
38
|
+
if limit:
|
|
39
|
+
params["limit"] = limit
|
|
40
|
+
|
|
41
|
+
return self._paginate(url, params)
|
|
42
|
+
|
|
43
|
+
def get_pages(
|
|
44
|
+
self,
|
|
45
|
+
page_ids: List[int] = None,
|
|
46
|
+
space_ids: List[int] = None,
|
|
47
|
+
statuses: List[str] = None,
|
|
48
|
+
title: str = None,
|
|
49
|
+
sort_condition: str = None,
|
|
50
|
+
limit: int = None,
|
|
51
|
+
) -> List[dict]:
|
|
52
|
+
url = f"{self.url}/wiki/api/v2/pages"
|
|
53
|
+
params = {
|
|
54
|
+
"body-format": "storage",
|
|
55
|
+
}
|
|
56
|
+
if page_ids:
|
|
57
|
+
params["id"] = page_ids
|
|
58
|
+
if space_ids:
|
|
59
|
+
params["space-id"] = space_ids
|
|
60
|
+
if statuses:
|
|
61
|
+
params["status"] = statuses
|
|
62
|
+
if title:
|
|
63
|
+
params["title"] = title
|
|
64
|
+
if sort_condition:
|
|
65
|
+
params["sort"] = sort_condition
|
|
66
|
+
if limit:
|
|
67
|
+
params["limit"] = limit
|
|
68
|
+
|
|
69
|
+
return self._paginate(url, params)
|
|
70
|
+
|
|
71
|
+
def get_blogposts(
|
|
72
|
+
self,
|
|
73
|
+
post_ids: List[int] = None,
|
|
74
|
+
space_ids: List[str] = None,
|
|
75
|
+
statuses: List[str] = None,
|
|
76
|
+
title: str = None,
|
|
77
|
+
sort_condition: str = None,
|
|
78
|
+
limit: int = None,
|
|
79
|
+
) -> List[dict]:
|
|
80
|
+
url = f"{self.url}/wiki/api/v2/blogposts"
|
|
81
|
+
params = {
|
|
82
|
+
"body-format": "storage",
|
|
83
|
+
}
|
|
84
|
+
if post_ids:
|
|
85
|
+
params["id"] = post_ids
|
|
86
|
+
if space_ids:
|
|
87
|
+
params["space-id"] = space_ids
|
|
88
|
+
if statuses:
|
|
89
|
+
params["status"] = statuses
|
|
90
|
+
if title:
|
|
91
|
+
params["title"] = title
|
|
92
|
+
if sort_condition:
|
|
93
|
+
params["sort"] = sort_condition
|
|
94
|
+
if limit:
|
|
95
|
+
params["limit"] = limit
|
|
96
|
+
|
|
97
|
+
return self._paginate(url, params)
|
|
98
|
+
|
|
99
|
+
def get_whiteboard_by_id(self, whiteboard_id: int) -> dict:
|
|
100
|
+
url = f"{self.url}/wiki/api/v2/whiteboards/{whiteboard_id}"
|
|
101
|
+
|
|
102
|
+
return self._make_request("GET", url)
|
|
103
|
+
|
|
104
|
+
def get_database_by_id(self, database_id: int) -> dict:
|
|
105
|
+
url = f"{self.url}/wiki/api/v2/databases/{database_id}"
|
|
106
|
+
|
|
107
|
+
return self._make_request("GET", url)
|
|
108
|
+
|
|
109
|
+
def get_tasks(
|
|
110
|
+
self,
|
|
111
|
+
task_ids: List[int] = None,
|
|
112
|
+
space_ids: List[str] = None,
|
|
113
|
+
page_ids: List[str] = None,
|
|
114
|
+
blogpost_ids: List[str] = None,
|
|
115
|
+
created_by_ids: List[str] = None,
|
|
116
|
+
assigned_to_ids: List[str] = None,
|
|
117
|
+
completed_by_ids: List[str] = None,
|
|
118
|
+
status: str = None,
|
|
119
|
+
limit: int = None,
|
|
120
|
+
) -> List[dict]:
|
|
121
|
+
url = f"{self.url}/wiki/api/v2/tasks"
|
|
122
|
+
params = {
|
|
123
|
+
"body-format": "storage",
|
|
124
|
+
}
|
|
125
|
+
if task_ids:
|
|
126
|
+
params["id"] = task_ids
|
|
127
|
+
if space_ids:
|
|
128
|
+
params["space-id"] = space_ids
|
|
129
|
+
if page_ids:
|
|
130
|
+
params["page-id"] = page_ids
|
|
131
|
+
if blogpost_ids:
|
|
132
|
+
params["blogpost-id"] = blogpost_ids
|
|
133
|
+
if created_by_ids:
|
|
134
|
+
params["created-by"] = created_by_ids
|
|
135
|
+
if assigned_to_ids:
|
|
136
|
+
params["assigned-to"] = assigned_to_ids
|
|
137
|
+
if completed_by_ids:
|
|
138
|
+
params["completed-by"] = completed_by_ids
|
|
139
|
+
if status:
|
|
140
|
+
params["status"] = status
|
|
141
|
+
if limit:
|
|
142
|
+
params["limit"] = limit
|
|
143
|
+
|
|
144
|
+
return self._paginate(url, params)
|
|
145
|
+
|
|
146
|
+
def _paginate(self, url: str, params: dict = None) -> List[dict]:
|
|
147
|
+
results = []
|
|
148
|
+
response = self._make_request("GET", url, params)
|
|
149
|
+
results.extend(response["results"])
|
|
150
|
+
|
|
151
|
+
while response["_links"].get("next"):
|
|
152
|
+
params["cursor"] = response["_links"].get("next")
|
|
153
|
+
response = self._make_request("GET", url, params)
|
|
154
|
+
results.extend(response["results"])
|
|
155
|
+
|
|
156
|
+
return results
|
|
157
|
+
|
|
158
|
+
def _make_request(self, method: str, url: str, params: dict = None, data: dict = None) -> dict:
|
|
159
|
+
response = self.session.request(method, url, params=params, json=data)
|
|
160
|
+
|
|
161
|
+
if response.status_code != 200:
|
|
162
|
+
raise Exception(f"Request failed with status code {response.status_code}: {response.text}")
|
|
163
|
+
|
|
164
|
+
return response.json()
|
|
@@ -1,83 +1,92 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Any, Dict
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from mindsdb.integrations.handlers.confluence_handler.confluence_table import (
|
|
3
|
+
from mindsdb.integrations.handlers.confluence_handler.confluence_api_client import ConfluenceAPIClient
|
|
4
|
+
from mindsdb.integrations.handlers.confluence_handler.confluence_tables import (
|
|
5
|
+
ConfluenceBlogPostsTable,
|
|
6
|
+
ConfluenceDatabasesTable,
|
|
8
7
|
ConfluencePagesTable,
|
|
8
|
+
ConfluenceSpacesTable,
|
|
9
|
+
ConfluenceTasksTable,
|
|
10
|
+
ConfluenceWhiteboardsTable,
|
|
9
11
|
)
|
|
10
12
|
from mindsdb.integrations.libs.api_handler import APIHandler
|
|
11
13
|
from mindsdb.integrations.libs.response import (
|
|
12
14
|
HandlerStatusResponse as StatusResponse,
|
|
13
15
|
)
|
|
14
|
-
from mindsdb_sql_parser import parse_sql
|
|
15
16
|
from mindsdb.utilities import log
|
|
16
17
|
|
|
17
|
-
from atlassian import Confluence
|
|
18
|
-
from typing import Optional
|
|
19
|
-
import requests
|
|
20
18
|
|
|
21
19
|
logger = log.getLogger(__name__)
|
|
22
20
|
|
|
21
|
+
|
|
23
22
|
class ConfluenceHandler(APIHandler):
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
----------
|
|
30
|
-
name : str
|
|
31
|
-
name of a handler instance
|
|
32
|
-
"""
|
|
33
|
-
super().__init__(name)
|
|
23
|
+
"""
|
|
24
|
+
This handler handles the connection and execution of SQL statements on Confluence.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name = "confluence"
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
def __init__(self, name: str, connection_data: Dict, **kwargs: Any) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Initializes the handler.
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
Args:
|
|
34
|
+
name (str): The name of the handler instance.
|
|
35
|
+
connection_data (Dict): The connection data required to connect to the Confluence API.
|
|
36
|
+
kwargs: Arbitrary keyword arguments.
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(name)
|
|
39
39
|
self.connection_data = connection_data
|
|
40
40
|
self.kwargs = kwargs
|
|
41
|
+
|
|
41
42
|
self.connection = None
|
|
42
43
|
self.is_connected = False
|
|
44
|
+
self.thread_safe = True
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
self._register_table("pages",
|
|
46
|
+
self._register_table("spaces", ConfluenceSpacesTable(self))
|
|
47
|
+
self._register_table("pages", ConfluencePagesTable(self))
|
|
48
|
+
self._register_table("blogposts", ConfluenceBlogPostsTable(self))
|
|
49
|
+
self._register_table("whiteboards", ConfluenceWhiteboardsTable(self))
|
|
50
|
+
self._register_table("databases", ConfluenceDatabasesTable(self))
|
|
51
|
+
self._register_table("tasks", ConfluenceTasksTable(self))
|
|
46
52
|
|
|
47
|
-
def connect(self):
|
|
48
|
-
"""
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
connection
|
|
53
|
+
def connect(self) -> ConfluenceAPIClient:
|
|
54
|
+
"""
|
|
55
|
+
Establishes a connection to the Confluence API.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If the required connection parameters are not provided.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
atlassian.confluence.Confluence: A connection object to the Confluence API.
|
|
53
62
|
"""
|
|
54
63
|
if self.is_connected is True:
|
|
55
64
|
return self.connection
|
|
56
|
-
|
|
57
|
-
if not all(key in self.connection_data and self.connection_data.get(key) for key in ['
|
|
58
|
-
raise ValueError('Required parameters (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
url=self.connection_data.get('
|
|
65
|
+
|
|
66
|
+
if not all(key in self.connection_data and self.connection_data.get(key) for key in ['api_base', 'username', 'password']):
|
|
67
|
+
raise ValueError('Required parameters (api_base, username, password) must be provided and should not be empty.')
|
|
68
|
+
|
|
69
|
+
self.connection = ConfluenceAPIClient(
|
|
70
|
+
url=self.connection_data.get('api_base'),
|
|
62
71
|
username=self.connection_data.get('username'),
|
|
63
72
|
password=self.connection_data.get('password'),
|
|
64
73
|
)
|
|
65
|
-
|
|
74
|
+
|
|
66
75
|
self.is_connected = True
|
|
67
76
|
return self.connection
|
|
68
77
|
|
|
69
78
|
def check_connection(self) -> StatusResponse:
|
|
70
|
-
"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
"""
|
|
80
|
+
Checks the status of the connection to the Confluence API.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
StatusResponse: An object containing the success status and an error message if an error occurs.
|
|
75
84
|
"""
|
|
76
85
|
response = StatusResponse(False)
|
|
77
|
-
need_to_close = self.is_connected is False
|
|
78
86
|
|
|
79
87
|
try:
|
|
80
|
-
self.connect()
|
|
88
|
+
connection = self.connect()
|
|
89
|
+
connection.get_spaces(limit=1)
|
|
81
90
|
response.success = True
|
|
82
91
|
except Exception as e:
|
|
83
92
|
logger.error(f"Error connecting to Confluence API: {e}!")
|
|
@@ -86,17 +95,3 @@ class ConfluenceHandler(APIHandler):
|
|
|
86
95
|
self.is_connected = response.success
|
|
87
96
|
|
|
88
97
|
return response
|
|
89
|
-
|
|
90
|
-
def native_query(self, query: str) -> StatusResponse:
|
|
91
|
-
"""Receive and process a raw query.
|
|
92
|
-
Parameters
|
|
93
|
-
----------
|
|
94
|
-
query : str
|
|
95
|
-
query in a native format
|
|
96
|
-
Returns
|
|
97
|
-
-------
|
|
98
|
-
StatusResponse
|
|
99
|
-
Request status
|
|
100
|
-
"""
|
|
101
|
-
ast = parse_sql(query)
|
|
102
|
-
return self.query(ast)
|