MindsDB 25.7.4.0__py3-none-any.whl → 25.8.3.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 +13 -1
- mindsdb/api/a2a/agent.py +6 -16
- mindsdb/api/a2a/common/types.py +3 -4
- mindsdb/api/a2a/task_manager.py +24 -35
- mindsdb/api/a2a/utils.py +63 -0
- mindsdb/api/executor/command_executor.py +9 -15
- 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 +2 -1
- mindsdb/api/http/namespaces/agents.py +6 -7
- mindsdb/api/http/namespaces/views.py +56 -72
- mindsdb/integrations/handlers/db2_handler/db2_handler.py +19 -23
- 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/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 +1 -2
- 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/salesforce_handler/constants.py +9 -2
- mindsdb/integrations/libs/llm/config.py +0 -14
- mindsdb/integrations/libs/llm/utils.py +0 -15
- mindsdb/integrations/utilities/files/file_reader.py +5 -19
- mindsdb/integrations/utilities/rag/rerankers/base_reranker.py +1 -1
- mindsdb/interfaces/agents/agents_controller.py +83 -45
- mindsdb/interfaces/agents/constants.py +16 -3
- mindsdb/interfaces/agents/langchain_agent.py +84 -21
- mindsdb/interfaces/database/projects.py +111 -7
- mindsdb/interfaces/knowledge_base/controller.py +7 -1
- 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 +14 -15
- mindsdb/interfaces/skills/custom/text2sql/mindsdb_sql_toolkit.py +7 -1
- mindsdb/interfaces/skills/skill_tool.py +7 -1
- mindsdb/interfaces/skills/sql_agent.py +6 -2
- mindsdb/utilities/config.py +2 -0
- mindsdb/utilities/fs.py +60 -17
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/METADATA +277 -262
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/RECORD +57 -56
- 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.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/WHEEL +0 -0
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.7.4.0.dist-info → mindsdb-25.8.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from mindsdb_sql_parser.ast import Identifier, Function, Constant, BinaryOperation
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def adapt_char_fn(node: Function) -> Function | None:
|
|
5
|
+
"""Replace MySQL's multy-arg CHAR call to chain of DuckDB's CHR calls
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
CHAR(77, 78, 79) => CHR(77) || CHR(78) || CHR(79)
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
node (Function): Function node to adapt
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
Function | None: Adapted function node
|
|
15
|
+
"""
|
|
16
|
+
if len(node.args) == 1:
|
|
17
|
+
node.op = "chr"
|
|
18
|
+
return node
|
|
19
|
+
|
|
20
|
+
acc = None
|
|
21
|
+
for arg in node.args:
|
|
22
|
+
fn = Function(op="chr", args=[arg])
|
|
23
|
+
if acc is None:
|
|
24
|
+
acc = fn
|
|
25
|
+
continue
|
|
26
|
+
acc = BinaryOperation("||", args=[acc, fn])
|
|
27
|
+
|
|
28
|
+
acc.parentheses = True
|
|
29
|
+
acc.alias = node.alias
|
|
30
|
+
return acc
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def adapt_locate_fn(node: Function) -> Function | None:
|
|
34
|
+
"""Replace MySQL's LOCATE (or INSTR) call to DuckDB's STRPOS call
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
LOCATE('bar', 'foobarbar') => STRPOS('foobarbar', 'bar')
|
|
38
|
+
INSTR('foobarbar', 'bar') => STRPOS('foobarbar', 'bar')
|
|
39
|
+
LOCATE('bar', 'foobarbar', 3) => ValueError (there is no analogue in DuckDB)
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
node (Function): Function node to adapt
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Function | None: Adapted function node
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
ValueError: If the function has 3 arguments
|
|
49
|
+
"""
|
|
50
|
+
if len(node.args) == 3:
|
|
51
|
+
raise ValueError("MySQL LOCATE function with 3 arguments is not supported")
|
|
52
|
+
if node.op == "locate":
|
|
53
|
+
node.args = [node.args[1], node.args[0]]
|
|
54
|
+
elif node.op == "insrt":
|
|
55
|
+
node.args = [node.args[0], node.args[1]]
|
|
56
|
+
node.op = "strpos"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def adapt_unhex_fn(node: Function) -> None:
|
|
60
|
+
"""Check MySQL's UNHEX function call arguments to ensure they are strings,
|
|
61
|
+
because DuckDB's UNHEX accepts only string arguments, while MySQL's UNHEX can accept integer arguments.
|
|
62
|
+
NOTE: if return dataframe from duckdb then unhex values are array - this may be an issue
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
node (Function): Function node to adapt
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
None
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
ValueError: If the function argument is not a string
|
|
72
|
+
"""
|
|
73
|
+
for arg in node.args:
|
|
74
|
+
if not isinstance(arg, (str, bytes)):
|
|
75
|
+
raise ValueError("MySQL UNHEX function argument must be a string")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def adapt_format_fn(node: Function) -> None:
|
|
79
|
+
"""Adapt MySQL's FORMAT function to DuckDB's FORMAT function
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
FORMAT(1234567.89, 0) => FORMAT('{:,.0f}', 1234567.89)
|
|
83
|
+
FORMAT(1234567.89, 2) => FORMAT('{:,.2f}', 1234567.89)
|
|
84
|
+
FORMAT(name, 2) => FORMAT('{:,.2f}', name)
|
|
85
|
+
FORMAT('{:.2f}', 1234567.89) => FORMAT('{:,.2f}', 1234567.89) # no changes for original style
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
node (Function): Function node to adapt
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
None
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
ValueError: If MySQL's function has 3rd 'locale' argument, like FORMAT(12332.2, 2, 'de_DE')
|
|
95
|
+
"""
|
|
96
|
+
match node.args[0], node.args[1]:
|
|
97
|
+
case Constant(value=(int() | float())), Constant(value=int()):
|
|
98
|
+
...
|
|
99
|
+
case Identifier(), Constant(value=int()):
|
|
100
|
+
...
|
|
101
|
+
case _:
|
|
102
|
+
return node
|
|
103
|
+
|
|
104
|
+
if len(node.args) > 2:
|
|
105
|
+
raise ValueError("'locale' argument of 'format' function is not supported")
|
|
106
|
+
decimal_places = node.args[1].value
|
|
107
|
+
|
|
108
|
+
if isinstance(node.args[0], Constant):
|
|
109
|
+
node.args[1].value = node.args[0].value
|
|
110
|
+
node.args[0].value = f"{{:,.{decimal_places}f}}"
|
|
111
|
+
else:
|
|
112
|
+
node.args[1] = node.args[0]
|
|
113
|
+
node.args[0] = Constant(f"{{:,.{decimal_places}f}}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def adapt_sha2_fn(node: Function) -> None:
|
|
117
|
+
"""Adapt MySQL's SHA2 function to DuckDB's SHA256 function
|
|
118
|
+
|
|
119
|
+
Example:
|
|
120
|
+
SHA2('test', 256) => SHA256('test')
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
node (Function): Function node to adapt
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
None
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
ValueError: If the function has more than 1 argument or the argument is not 256
|
|
130
|
+
"""
|
|
131
|
+
if len(node.args) > 1 and node.args[1].value != 256:
|
|
132
|
+
raise ValueError("Only sha256 is supported")
|
|
133
|
+
node.op = "sha256"
|
|
134
|
+
node.args = [node.args[0]]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def adapt_length_fn(node: Function) -> None:
|
|
138
|
+
"""Adapt MySQL's LENGTH function to DuckDB's STRLEN function
|
|
139
|
+
NOTE: duckdb also have LENGTH, therefore it can not be used
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
LENGTH('test') => STRLEN('test')
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
node (Function): Function node to adapt
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
None
|
|
149
|
+
"""
|
|
150
|
+
node.op = "strlen"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def adapt_regexp_substr_fn(node: Function) -> None:
|
|
154
|
+
"""Adapt MySQL's REGEXP_SUBSTR function to DuckDB's REGEXP_EXTRACT function
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
REGEXP_SUBSTR('foobarbar', 'bar', 1, 1) => REGEXP_EXTRACT('foobarbar', 'bar')
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
node (Function): Function node to adapt
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
None
|
|
164
|
+
|
|
165
|
+
Raises:
|
|
166
|
+
ValueError: If the function has more than 2 arguments or 3rd or 4th argument is not 1
|
|
167
|
+
"""
|
|
168
|
+
if (
|
|
169
|
+
len(node.args) == 3
|
|
170
|
+
and node.args[2].value != 1
|
|
171
|
+
or len(node.args) == 4
|
|
172
|
+
and (node.args[3].value != 1 or node.args[2].value != 1)
|
|
173
|
+
or len(node.args) > 4
|
|
174
|
+
):
|
|
175
|
+
raise ValueError("Only 2 arguments are supported for REGEXP_SUBSTR function")
|
|
176
|
+
node.args = node.args[:2]
|
|
177
|
+
node.op = "regexp_extract"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def adapt_substring_index_fn(node: Function) -> BinaryOperation | Function:
|
|
181
|
+
"""Adapt MySQL's SUBSTRING_INDEX function to DuckDB's SPLIT_PART function
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
SUBSTRING_INDEX('a.b.c.d', '.', 1) => SPLIT_PART('a.b.c.d', '.', 1)
|
|
185
|
+
SUBSTRING_INDEX('a.b.c.d', '.', 2) => CONCAT_WS('.', SPLIT_PART('a.b.c.d', '.', 1), SPLIT_PART('a.b.c.d', '.', 2))
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
node (Function): Function node to adapt
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
BinaryOperation | Function: Binary operation node or function node
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
ValueError: If the function has more than 3 arguments or the 3rd argument is not 1
|
|
195
|
+
"""
|
|
196
|
+
if len(node.args[1].value) > 1:
|
|
197
|
+
raise ValueError("Only one car in separator")
|
|
198
|
+
|
|
199
|
+
if node.args[2].value == 1:
|
|
200
|
+
node.op = "split_part"
|
|
201
|
+
return node
|
|
202
|
+
|
|
203
|
+
acc = [node.args[1]]
|
|
204
|
+
for i in range(node.args[2].value):
|
|
205
|
+
fn = Function(op="split_part", args=[node.args[0], node.args[1], Constant(i + 1)])
|
|
206
|
+
acc.append(fn)
|
|
207
|
+
|
|
208
|
+
acc = Function(op="concat_ws", args=acc)
|
|
209
|
+
acc.alias = node.alias
|
|
210
|
+
return acc
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def adapt_curtime_fn(node: Function) -> BinaryOperation:
|
|
214
|
+
"""Adapt MySQL's CURTIME function to DuckDB's GET_CURRENT_TIME function.
|
|
215
|
+
To get the same type as MySQL's CURTIME function, we need to cast the result to time type.
|
|
216
|
+
|
|
217
|
+
Example:
|
|
218
|
+
CURTIME() => GET_CURRENT_TIME()::time
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
node (Function): Function node to adapt
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
BinaryOperation: Binary operation node
|
|
225
|
+
"""
|
|
226
|
+
return BinaryOperation("::", args=[Function(op="get_current_time", args=[]), Identifier("time")], alias=node.alias)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def adapt_timestampdiff_fn(node: Function) -> None:
|
|
230
|
+
"""Adapt MySQL's TIMESTAMPDIFF function to DuckDB's DATE_DIFF function
|
|
231
|
+
NOTE: Looks like cast string args to timestamp works in most cases, but there may be some exceptions.
|
|
232
|
+
|
|
233
|
+
Example:
|
|
234
|
+
TIMESTAMPDIFF(YEAR, '2000-02-01', '2003-05-01') => DATE_DIFF('year', timestamp '2000-02-01', timestamp '2003-05-01')
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
node (Function): Function node to adapt
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
None
|
|
241
|
+
"""
|
|
242
|
+
node.op = "date_diff"
|
|
243
|
+
node.args[0] = Constant(node.args[0].parts[0])
|
|
244
|
+
node.args[1] = BinaryOperation(" ", args=[Identifier("timestamp"), node.args[1]])
|
|
245
|
+
node.args[2] = BinaryOperation(" ", args=[Identifier("timestamp"), node.args[2]])
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def adapt_extract_fn(node: Function) -> None:
|
|
249
|
+
"""Adapt MySQL's EXTRACT function to DuckDB's EXTRACT function
|
|
250
|
+
TODO: multi-part args, like YEAR_MONTH, is not supported yet
|
|
251
|
+
NOTE: Looks like adding 'timestamp' works in most cases, but there may be some exceptions.
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
EXTRACT(YEAR FROM '2000-02-01') => EXTRACT('year' from timestamp '2000-02-01')
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
node (Function): Function node to adapt
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
None
|
|
261
|
+
"""
|
|
262
|
+
node.args[0] = Constant(node.args[0].parts[0])
|
|
263
|
+
if not isinstance(node.from_arg, Identifier):
|
|
264
|
+
node.from_arg = BinaryOperation(" ", args=[Identifier("timestamp"), node.from_arg])
|
|
@@ -14,6 +14,19 @@ from mindsdb.utilities.exception import format_db_error_message
|
|
|
14
14
|
from mindsdb.utilities.functions import resolve_table_identifier, resolve_model_identifier
|
|
15
15
|
from mindsdb.utilities.json_encoder import CustomJSONEncoder
|
|
16
16
|
from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
|
|
17
|
+
from mindsdb.api.executor.utilities.mysql_to_duckdb_functions import (
|
|
18
|
+
adapt_char_fn,
|
|
19
|
+
adapt_locate_fn,
|
|
20
|
+
adapt_unhex_fn,
|
|
21
|
+
adapt_format_fn,
|
|
22
|
+
adapt_sha2_fn,
|
|
23
|
+
adapt_length_fn,
|
|
24
|
+
adapt_regexp_substr_fn,
|
|
25
|
+
adapt_substring_index_fn,
|
|
26
|
+
adapt_curtime_fn,
|
|
27
|
+
adapt_timestampdiff_fn,
|
|
28
|
+
adapt_extract_fn,
|
|
29
|
+
)
|
|
17
30
|
|
|
18
31
|
logger = log.getLogger(__name__)
|
|
19
32
|
|
|
@@ -185,6 +198,23 @@ def query_df(df, query, session=None):
|
|
|
185
198
|
if isinstance(node, Function):
|
|
186
199
|
fnc_name = node.op.lower()
|
|
187
200
|
|
|
201
|
+
mysql_to_duck_fn_map = {
|
|
202
|
+
"char": adapt_char_fn,
|
|
203
|
+
"locate": adapt_locate_fn,
|
|
204
|
+
"insrt": adapt_locate_fn,
|
|
205
|
+
"unhex": adapt_unhex_fn,
|
|
206
|
+
"format": adapt_format_fn,
|
|
207
|
+
"sha2": adapt_sha2_fn,
|
|
208
|
+
"length": adapt_length_fn,
|
|
209
|
+
"regexp_substr": adapt_regexp_substr_fn,
|
|
210
|
+
"substring_index": adapt_substring_index_fn,
|
|
211
|
+
"curtime": adapt_curtime_fn,
|
|
212
|
+
"timestampdiff": adapt_timestampdiff_fn,
|
|
213
|
+
"extract": adapt_extract_fn,
|
|
214
|
+
}
|
|
215
|
+
if fnc_name in mysql_to_duck_fn_map:
|
|
216
|
+
return mysql_to_duck_fn_map[fnc_name](node)
|
|
217
|
+
|
|
188
218
|
if fnc_name == "database" and len(node.args) == 0:
|
|
189
219
|
if session is not None:
|
|
190
220
|
cur_db = session.database
|
mindsdb/api/http/initialize.py
CHANGED
|
@@ -323,15 +323,16 @@ class AgentCompletionsStream(Resource):
|
|
|
323
323
|
@ns_conf.doc("agent_completions_stream")
|
|
324
324
|
@api_endpoint_metrics("POST", "/agents/agent/completions/stream")
|
|
325
325
|
def post(self, project_name, agent_name):
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
# Check for required parameters.
|
|
326
|
+
# Extract messages from request (HTTP format only)
|
|
329
327
|
if "messages" not in request.json:
|
|
330
|
-
logger.error("Missing 'messages' parameter in request body")
|
|
331
328
|
return http_error(
|
|
332
|
-
HTTPStatus.BAD_REQUEST,
|
|
329
|
+
HTTPStatus.BAD_REQUEST,
|
|
330
|
+
"Missing parameter",
|
|
331
|
+
'Must provide "messages" parameter in POST body',
|
|
333
332
|
)
|
|
334
333
|
|
|
334
|
+
messages = request.json["messages"]
|
|
335
|
+
|
|
335
336
|
session = SessionController()
|
|
336
337
|
try:
|
|
337
338
|
existing_agent = session.agents_controller.get_agent(agent_name, project_name=project_name)
|
|
@@ -346,8 +347,6 @@ class AgentCompletionsStream(Resource):
|
|
|
346
347
|
HTTPStatus.NOT_FOUND, "Project not found", f"Project with name {project_name} does not exist"
|
|
347
348
|
)
|
|
348
349
|
|
|
349
|
-
messages = request.json["messages"]
|
|
350
|
-
|
|
351
350
|
try:
|
|
352
351
|
gen = _completion_event_generator(agent_name, messages, project_name)
|
|
353
352
|
logger.info(f"Starting streaming response for agent {agent_name}")
|
|
@@ -10,143 +10,127 @@ from mindsdb.metrics.metrics import api_endpoint_metrics
|
|
|
10
10
|
from mindsdb.utilities.exception import EntityNotExistsError
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
@ns_conf.route(
|
|
13
|
+
@ns_conf.route("/<project_name>/views")
|
|
14
14
|
class ViewsList(Resource):
|
|
15
|
-
@ns_conf.doc(
|
|
16
|
-
@api_endpoint_metrics(
|
|
15
|
+
@ns_conf.doc("list_views")
|
|
16
|
+
@api_endpoint_metrics("GET", "/views")
|
|
17
17
|
def get(self, project_name):
|
|
18
|
-
|
|
18
|
+
"""List all views"""
|
|
19
19
|
session = SessionController()
|
|
20
20
|
try:
|
|
21
21
|
project = session.database_controller.get_project(project_name)
|
|
22
22
|
except EntityNotExistsError:
|
|
23
|
-
return http_error(
|
|
24
|
-
HTTPStatus.NOT_FOUND,
|
|
25
|
-
'Project not found',
|
|
26
|
-
f'Project name {project_name} does not exist'
|
|
27
|
-
)
|
|
23
|
+
return http_error(HTTPStatus.NOT_FOUND, "Project not found", f"Project name {project_name} does not exist")
|
|
28
24
|
|
|
29
25
|
all_views = project.get_views()
|
|
30
26
|
all_view_objs = []
|
|
31
27
|
# Only want to return relevant fields to the user.
|
|
32
28
|
for view in all_views:
|
|
33
|
-
all_view_objs.append({
|
|
34
|
-
'id': view['metadata']['id'],
|
|
35
|
-
'name': view['name'],
|
|
36
|
-
'query': view['query']
|
|
37
|
-
})
|
|
29
|
+
all_view_objs.append({"id": view["metadata"]["id"], "name": view["name"], "query": view["query"]})
|
|
38
30
|
return all_view_objs
|
|
39
31
|
|
|
40
|
-
@ns_conf.doc(
|
|
41
|
-
@api_endpoint_metrics(
|
|
32
|
+
@ns_conf.doc("create_view")
|
|
33
|
+
@api_endpoint_metrics("POST", "/views")
|
|
42
34
|
def post(self, project_name):
|
|
43
|
-
|
|
44
|
-
if
|
|
45
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
35
|
+
"""Create a new view"""
|
|
36
|
+
if "view" not in request.json:
|
|
37
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Must provide "view" parameter in POST body')
|
|
46
38
|
session = SessionController()
|
|
47
|
-
view_obj = request.json[
|
|
48
|
-
if
|
|
49
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
50
|
-
if
|
|
51
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
52
|
-
name = view_obj[
|
|
53
|
-
query = view_obj[
|
|
39
|
+
view_obj = request.json["view"]
|
|
40
|
+
if "name" not in view_obj:
|
|
41
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "name" field for view')
|
|
42
|
+
if "query" not in view_obj:
|
|
43
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "query" field for view')
|
|
44
|
+
name = view_obj["name"]
|
|
45
|
+
query = view_obj["query"]
|
|
54
46
|
|
|
55
47
|
try:
|
|
56
48
|
project = session.database_controller.get_project(project_name)
|
|
57
49
|
except EntityNotExistsError:
|
|
58
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
50
|
+
return http_error(HTTPStatus.NOT_FOUND, "Not found", f"Project name {project_name} does not exist")
|
|
59
51
|
|
|
60
52
|
if project.get_view(name) is not None:
|
|
61
|
-
return http_error(HTTPStatus.CONFLICT,
|
|
53
|
+
return http_error(HTTPStatus.CONFLICT, "Name conflict", f"View with name {name} already exists.")
|
|
62
54
|
|
|
63
|
-
project.create_view(name, query)
|
|
55
|
+
project.create_view(name, query, session)
|
|
64
56
|
created_view = project.get_view(name)
|
|
65
57
|
# Only want to return relevant fields to the user.
|
|
66
58
|
return {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
59
|
+
"id": created_view["metadata"]["id"],
|
|
60
|
+
"name": created_view["name"],
|
|
61
|
+
"query": created_view["query"],
|
|
70
62
|
}, HTTPStatus.CREATED
|
|
71
63
|
|
|
72
64
|
|
|
73
|
-
@ns_conf.route(
|
|
74
|
-
@ns_conf.param(
|
|
75
|
-
@ns_conf.param(
|
|
65
|
+
@ns_conf.route("/<project_name>/views/<view_name>")
|
|
66
|
+
@ns_conf.param("project_name", "Name of the project")
|
|
67
|
+
@ns_conf.param("view_name", "Name of the view")
|
|
76
68
|
class ViewResource(Resource):
|
|
77
|
-
@ns_conf.doc(
|
|
78
|
-
@api_endpoint_metrics(
|
|
69
|
+
@ns_conf.doc("get_view")
|
|
70
|
+
@api_endpoint_metrics("GET", "/views/view")
|
|
79
71
|
def get(self, project_name, view_name):
|
|
80
|
-
|
|
72
|
+
"""Get a view by name"""
|
|
81
73
|
session = SessionController()
|
|
82
74
|
try:
|
|
83
75
|
project = session.database_controller.get_project(project_name)
|
|
84
76
|
except EntityNotExistsError:
|
|
85
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
77
|
+
return http_error(HTTPStatus.NOT_FOUND, "Project not found", f"Project name {project_name} does not exist")
|
|
86
78
|
|
|
87
79
|
view = project.get_view(view_name)
|
|
88
80
|
if view is None:
|
|
89
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
81
|
+
return http_error(HTTPStatus.NOT_FOUND, "View not found", f"View with name {view_name} does not exist")
|
|
90
82
|
|
|
91
83
|
# Only want to return relevant fields to the user.
|
|
92
|
-
return {
|
|
93
|
-
'id': view['metadata']['id'],
|
|
94
|
-
'name': view['name'],
|
|
95
|
-
'query': view['query']
|
|
96
|
-
}
|
|
84
|
+
return {"id": view["metadata"]["id"], "name": view["name"], "query": view["query"]}
|
|
97
85
|
|
|
98
|
-
@ns_conf.doc(
|
|
99
|
-
@api_endpoint_metrics(
|
|
86
|
+
@ns_conf.doc("update_view")
|
|
87
|
+
@api_endpoint_metrics("PUT", "/views/view")
|
|
100
88
|
def put(self, project_name, view_name):
|
|
101
|
-
|
|
102
|
-
if
|
|
103
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
104
|
-
request_view = request.json[
|
|
89
|
+
"""Updates or creates a view"""
|
|
90
|
+
if "view" not in request.json:
|
|
91
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Must provide "view" parameter in PUT body')
|
|
92
|
+
request_view = request.json["view"]
|
|
105
93
|
session = SessionController()
|
|
106
94
|
try:
|
|
107
95
|
project = session.database_controller.get_project(project_name)
|
|
108
96
|
except EntityNotExistsError:
|
|
109
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
97
|
+
return http_error(HTTPStatus.NOT_FOUND, "Project not found", f"Project name {project_name} does not exist")
|
|
110
98
|
|
|
111
99
|
existing_view = project.get_view(view_name)
|
|
112
100
|
if existing_view is None:
|
|
113
101
|
# Create
|
|
114
|
-
if
|
|
115
|
-
return http_error(HTTPStatus.BAD_REQUEST,
|
|
116
|
-
project.create_view(view_name, request_view[
|
|
102
|
+
if "query" not in request_view:
|
|
103
|
+
return http_error(HTTPStatus.BAD_REQUEST, "Wrong argument", 'Missing "query" field for new view')
|
|
104
|
+
project.create_view(view_name, request_view["query"], session)
|
|
117
105
|
created_view = project.get_view(view_name)
|
|
118
106
|
# Only want to return relevant fields to the user.
|
|
119
107
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
108
|
+
"id": created_view["metadata"]["id"],
|
|
109
|
+
"name": created_view["name"],
|
|
110
|
+
"query": created_view["query"],
|
|
123
111
|
}, HTTPStatus.CREATED
|
|
124
112
|
|
|
125
|
-
new_query = existing_view[
|
|
126
|
-
if
|
|
127
|
-
new_query = request_view[
|
|
113
|
+
new_query = existing_view["query"]
|
|
114
|
+
if "query" in request_view:
|
|
115
|
+
new_query = request_view["query"]
|
|
128
116
|
project.update_view(view_name, new_query)
|
|
129
117
|
|
|
130
118
|
existing_view = project.get_view(view_name)
|
|
131
119
|
# Only want to return relevant fields to the user.
|
|
132
|
-
return {
|
|
133
|
-
'id': existing_view['metadata']['id'],
|
|
134
|
-
'name': existing_view['name'],
|
|
135
|
-
'query': existing_view['query']
|
|
136
|
-
}
|
|
120
|
+
return {"id": existing_view["metadata"]["id"], "name": existing_view["name"], "query": existing_view["query"]}
|
|
137
121
|
|
|
138
|
-
@ns_conf.doc(
|
|
139
|
-
@api_endpoint_metrics(
|
|
122
|
+
@ns_conf.doc("delete_view")
|
|
123
|
+
@api_endpoint_metrics("DELETE", "/views/view")
|
|
140
124
|
def delete(self, project_name, view_name):
|
|
141
|
-
|
|
125
|
+
"""Deletes a view by name"""
|
|
142
126
|
session = SessionController()
|
|
143
127
|
try:
|
|
144
128
|
project = session.database_controller.get_project(project_name)
|
|
145
129
|
except EntityNotExistsError:
|
|
146
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
130
|
+
return http_error(HTTPStatus.NOT_FOUND, "Project not found", f"Project name {project_name} does not exist")
|
|
147
131
|
|
|
148
132
|
if project.get_view(view_name) is None:
|
|
149
|
-
return http_error(HTTPStatus.NOT_FOUND,
|
|
133
|
+
return http_error(HTTPStatus.NOT_FOUND, "View not found", f"View with name {view_name} does not exist")
|
|
150
134
|
|
|
151
135
|
project.delete_view(view_name)
|
|
152
|
-
return
|
|
136
|
+
return "", HTTPStatus.NO_CONTENT
|
|
@@ -59,16 +59,20 @@ class DB2Handler(DatabaseHandler):
|
|
|
59
59
|
return self.connection
|
|
60
60
|
|
|
61
61
|
# Mandatory connection parameters.
|
|
62
|
-
if not all(key in self.connection_data for key in [
|
|
63
|
-
raise ValueError(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
connection_string
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
if not all(key in self.connection_data for key in ["host", "user", "password", "database"]):
|
|
63
|
+
raise ValueError("Required parameters (host, user, password, database) must be provided.")
|
|
64
|
+
cloud = "databases.appdomain.cloud" in self.connection_data["host"]
|
|
65
|
+
if cloud:
|
|
66
|
+
connection_string = f"DATABASE={self.connection_data['database']};HOSTNAME={self.connection_data['host']};PORT={self.connection_data['port']};PROTOCOL=TCPIP;UID={self.connection_data['user']};PWD={self.connection_data['password']};SECURITY=SSL;"
|
|
67
|
+
connection_string += "SSLSERVERCERTIFICATE=;"
|
|
68
|
+
else:
|
|
69
|
+
connection_string = f"DRIVER={'IBM DB2 ODBC DRIVER'};DATABASE={self.connection_data['database']};HOST={self.connection_data['host']};PROTOCOL=TCPIP;UID={self.connection_data['user']};PWD={self.connection_data['password']};"
|
|
70
|
+
|
|
71
|
+
# Optional connection parameters.
|
|
72
|
+
if "port" in self.connection_data:
|
|
73
|
+
connection_string += f"PORT={self.connection_data['port']};"
|
|
74
|
+
|
|
75
|
+
if "schema" in self.connection_data:
|
|
72
76
|
connection_string += f"CURRENTSCHEMA={self.connection_data['schema']};"
|
|
73
77
|
|
|
74
78
|
try:
|
|
@@ -106,10 +110,10 @@ class DB2Handler(DatabaseHandler):
|
|
|
106
110
|
self.connect()
|
|
107
111
|
response.success = True
|
|
108
112
|
except (OperationalError, ValueError) as known_error:
|
|
109
|
-
logger.error(f
|
|
113
|
+
logger.error(f"Connection check to IBM Db2 failed, {known_error}!")
|
|
110
114
|
response.error_message = str(known_error)
|
|
111
115
|
except Exception as unknown_error:
|
|
112
|
-
logger.error(f
|
|
116
|
+
logger.error(f"Connection check to IBM Db2 failed due to an unknown error, {unknown_error}!")
|
|
113
117
|
response.error_message = str(unknown_error)
|
|
114
118
|
|
|
115
119
|
if response.success and need_to_close:
|
|
@@ -141,9 +145,7 @@ class DB2Handler(DatabaseHandler):
|
|
|
141
145
|
result = cur.fetchall()
|
|
142
146
|
response = Response(
|
|
143
147
|
RESPONSE_TYPE.TABLE,
|
|
144
|
-
data_frame=pd.DataFrame(
|
|
145
|
-
result, columns=[x[0] for x in cur.description]
|
|
146
|
-
),
|
|
148
|
+
data_frame=pd.DataFrame(result, columns=[x[0] for x in cur.description]),
|
|
147
149
|
)
|
|
148
150
|
else:
|
|
149
151
|
response = Response(RESPONSE_TYPE.OK)
|
|
@@ -198,10 +200,7 @@ class DB2Handler(DatabaseHandler):
|
|
|
198
200
|
}
|
|
199
201
|
)
|
|
200
202
|
|
|
201
|
-
response = Response(
|
|
202
|
-
RESPONSE_TYPE.TABLE,
|
|
203
|
-
data_frame=pd.DataFrame(tables)
|
|
204
|
-
)
|
|
203
|
+
response = Response(RESPONSE_TYPE.TABLE, data_frame=pd.DataFrame(tables))
|
|
205
204
|
|
|
206
205
|
return response
|
|
207
206
|
|
|
@@ -227,9 +226,6 @@ class DB2Handler(DatabaseHandler):
|
|
|
227
226
|
|
|
228
227
|
columns = [column["COLUMN_NAME"] for column in result]
|
|
229
228
|
|
|
230
|
-
response = Response(
|
|
231
|
-
RESPONSE_TYPE.TABLE,
|
|
232
|
-
data_frame=pd.DataFrame(columns, columns=["COLUMN_NAME"])
|
|
233
|
-
)
|
|
229
|
+
response = Response(RESPONSE_TYPE.TABLE, data_frame=pd.DataFrame(columns, columns=["COLUMN_NAME"]))
|
|
234
230
|
|
|
235
231
|
return response
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from mindsdb.integrations.libs.const import HANDLER_TYPE
|
|
2
|
+
|
|
3
|
+
from .__about__ import __version__ as version, __description__ as description
|
|
4
|
+
from .connection_args import connection_args, connection_args_example
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
from .gong_handler import GongHandler as Handler
|
|
8
|
+
|
|
9
|
+
import_error = None
|
|
10
|
+
except Exception as e:
|
|
11
|
+
Handler = None
|
|
12
|
+
import_error = e
|
|
13
|
+
|
|
14
|
+
title = "Gong"
|
|
15
|
+
name = "gong"
|
|
16
|
+
type = HANDLER_TYPE.DATA
|
|
17
|
+
icon_path = "icon.svg"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Handler",
|
|
21
|
+
"version",
|
|
22
|
+
"name",
|
|
23
|
+
"type",
|
|
24
|
+
"title",
|
|
25
|
+
"description",
|
|
26
|
+
"import_error",
|
|
27
|
+
"icon_path",
|
|
28
|
+
"connection_args_example",
|
|
29
|
+
"connection_args",
|
|
30
|
+
]
|