MindsDB 25.3.4.1__py3-none-any.whl → 25.3.4.2__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/api/executor/datahub/datanodes/integration_datanode.py +5 -2
- mindsdb/api/executor/datahub/datanodes/system_tables.py +131 -138
- mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +74 -0
- mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +14 -2
- mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -55
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +52 -21
- mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +6 -29
- mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +37 -1
- mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +28 -1
- mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +53 -5
- mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +37 -1
- mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +42 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +1 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/__init__.py +1 -1
- mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +97 -18
- mindsdb/utilities/render/sqlalchemy_render.py +30 -6
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/METADATA +226 -228
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/RECORD +22 -22
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/WHEEL +0 -0
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/licenses/LICENSE +0 -0
- {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/top_level.txt +0 -0
|
@@ -13,10 +13,43 @@ from mindsdb.integrations.libs.response import (
|
|
|
13
13
|
HandlerResponse as Response,
|
|
14
14
|
RESPONSE_TYPE
|
|
15
15
|
)
|
|
16
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
logger = log.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
22
|
+
def _map_type(mssql_type_text: str) -> MYSQL_DATA_TYPE:
|
|
23
|
+
""" Map MSSQL text types names to MySQL types as enum.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
mssql_type_text (str): The name of the MSSQL type to map.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
MYSQL_DATA_TYPE: The MySQL type enum that corresponds to the MSSQL text type name.
|
|
30
|
+
"""
|
|
31
|
+
internal_type_name = mssql_type_text.lower()
|
|
32
|
+
types_map = {
|
|
33
|
+
('tinyint', 'smallint', 'int', 'bigint'): MYSQL_DATA_TYPE.INT,
|
|
34
|
+
('bit',): MYSQL_DATA_TYPE.BOOL,
|
|
35
|
+
('money', 'smallmoney', 'float', 'real'): MYSQL_DATA_TYPE.FLOAT,
|
|
36
|
+
('decimal', 'numeric'): MYSQL_DATA_TYPE.DECIMAL,
|
|
37
|
+
('date',): MYSQL_DATA_TYPE.DATE,
|
|
38
|
+
('time',): MYSQL_DATA_TYPE.TIME,
|
|
39
|
+
('datetime2', 'datetimeoffset', 'datetime', 'smalldatetime'): MYSQL_DATA_TYPE.DATETIME,
|
|
40
|
+
('varchar', 'nvarchar'): MYSQL_DATA_TYPE.VARCHAR,
|
|
41
|
+
('char', 'text', 'nchar', 'ntext'): MYSQL_DATA_TYPE.TEXT,
|
|
42
|
+
('binary', 'varbinary', 'image'): MYSQL_DATA_TYPE.BINARY
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for db_types_list, mysql_data_type in types_map.items():
|
|
46
|
+
if internal_type_name in db_types_list:
|
|
47
|
+
return mysql_data_type
|
|
48
|
+
|
|
49
|
+
logger.warning(f"MSSQL handler type mapping: unknown type: {internal_type_name}, use VARCHAR as fallback.")
|
|
50
|
+
return MYSQL_DATA_TYPE.VARCHAR
|
|
51
|
+
|
|
52
|
+
|
|
20
53
|
class SqlServerHandler(DatabaseHandler):
|
|
21
54
|
"""
|
|
22
55
|
This handler handles connection and execution of the Microsoft SQL Server statements.
|
|
@@ -215,4 +248,7 @@ class SqlServerHandler(DatabaseHandler):
|
|
|
215
248
|
WHERE
|
|
216
249
|
table_name = '{table_name}'
|
|
217
250
|
"""
|
|
218
|
-
|
|
251
|
+
result = self.native_query(query)
|
|
252
|
+
if result.resp_type is RESPONSE_TYPE.TABLE:
|
|
253
|
+
result.data_frame['mysql_data_type'] = result.data_frame['Type'].apply(_map_type)
|
|
254
|
+
return result
|
|
@@ -13,10 +13,27 @@ from mindsdb.integrations.libs.response import (
|
|
|
13
13
|
RESPONSE_TYPE
|
|
14
14
|
)
|
|
15
15
|
from mindsdb.integrations.handlers.mysql_handler.settings import ConnectionConfig
|
|
16
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
16
17
|
|
|
17
18
|
logger = log.getLogger(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
21
|
+
def _map_type(mysql_type_text: str) -> MYSQL_DATA_TYPE:
|
|
22
|
+
""" Map MySQL text types names to MySQL types as enum.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
mysql_type_text (str): The name of the MySQL type to map.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
MYSQL_DATA_TYPE: The MySQL type enum that corresponds to the MySQL text type name.
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
return MYSQL_DATA_TYPE(mysql_type_text.upper())
|
|
32
|
+
except Exception:
|
|
33
|
+
logger.warning(f'MySQL handler: unknown type: {mysql_type_text}, use TEXT as fallback.')
|
|
34
|
+
return MYSQL_DATA_TYPE.TEXT
|
|
35
|
+
|
|
36
|
+
|
|
20
37
|
class MySQLHandler(DatabaseHandler):
|
|
21
38
|
"""
|
|
22
39
|
This handler handles connection and execution of the MySQL statements.
|
|
@@ -211,6 +228,16 @@ class MySQLHandler(DatabaseHandler):
|
|
|
211
228
|
"""
|
|
212
229
|
Show details about the table
|
|
213
230
|
"""
|
|
214
|
-
q = f"
|
|
231
|
+
q = f"""
|
|
232
|
+
select
|
|
233
|
+
COLUMN_NAME AS FIELD, DATA_TYPE AS TYPE
|
|
234
|
+
from
|
|
235
|
+
information_schema.columns
|
|
236
|
+
where
|
|
237
|
+
table_name = '{table_name}'
|
|
238
|
+
"""
|
|
215
239
|
result = self.native_query(q)
|
|
240
|
+
if result.resp_type is RESPONSE_TYPE.TABLE:
|
|
241
|
+
result.data_frame = result.data_frame.rename(columns={'FIELD': 'Field', 'TYPE': 'Type'})
|
|
242
|
+
result.data_frame['mysql_data_type'] = result.data_frame['Type'].apply(_map_type)
|
|
216
243
|
return result
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
from typing import Text, Dict, Optional
|
|
2
2
|
|
|
3
|
-
from mindsdb_sql_parser.ast.base import ASTNode
|
|
4
|
-
from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
|
|
5
3
|
import oracledb
|
|
6
|
-
from oracledb import connect, Connection, DatabaseError
|
|
7
4
|
import pandas as pd
|
|
5
|
+
from oracledb import connect, Connection, DatabaseError
|
|
6
|
+
from mindsdb_sql_parser.ast.base import ASTNode
|
|
8
7
|
|
|
9
8
|
from mindsdb.integrations.libs.base import DatabaseHandler
|
|
10
9
|
from mindsdb.integrations.libs.response import (
|
|
@@ -13,12 +12,52 @@ from mindsdb.integrations.libs.response import (
|
|
|
13
12
|
RESPONSE_TYPE,
|
|
14
13
|
)
|
|
15
14
|
from mindsdb.utilities import log
|
|
15
|
+
from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
|
|
16
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
oracledb.defaults.fetch_lobs = False # Return LOBs directly as strings or bytes.
|
|
19
20
|
logger = log.getLogger(__name__)
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
def _map_type(internal_type_name: str) -> MYSQL_DATA_TYPE:
|
|
24
|
+
""" Map Oracle types to MySQL types.
|
|
25
|
+
List of types: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
internal_type_name (str): The name of the Oracle type to map.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
MYSQL_DATA_TYPE: The MySQL type that corresponds to the Oracle type.
|
|
32
|
+
"""
|
|
33
|
+
internal_type_name = internal_type_name.upper()
|
|
34
|
+
types_map = {
|
|
35
|
+
(
|
|
36
|
+
'VARCHAR2', 'NVARCHAR2', 'CHARACTER VARYING', 'CHAR VARYING', 'NATIONAL CHARACTER', 'NATIONAL CHAR',
|
|
37
|
+
'VARCHAR', 'NCHAR', 'NATIONAL CHARACTER VARYING', 'NATIONAL CHAR VARYING', 'NCHAR VARYING', 'LONG VARCHAR'
|
|
38
|
+
): MYSQL_DATA_TYPE.VARCHAR,
|
|
39
|
+
('INTEGER', 'INT'): MYSQL_DATA_TYPE.INT,
|
|
40
|
+
('SMALLINT',): MYSQL_DATA_TYPE.SMALLINT,
|
|
41
|
+
('NUMBER', 'DECIMAL'): MYSQL_DATA_TYPE.DECIMAL,
|
|
42
|
+
('FLOAT', 'BINARY_FLOAT', 'REAL'): MYSQL_DATA_TYPE.FLOAT,
|
|
43
|
+
('BINARY_DOUBLE',): MYSQL_DATA_TYPE.DOUBLE,
|
|
44
|
+
('LONG',): MYSQL_DATA_TYPE.BIGINT,
|
|
45
|
+
('DATE',): MYSQL_DATA_TYPE.DATE,
|
|
46
|
+
('HOUR', 'MINUTE', 'SECOND', 'TIMEZONE_HOUR', 'TIMEZONE_MINUTE'): MYSQL_DATA_TYPE.SMALLINT,
|
|
47
|
+
('TIMESTAMP', 'TIMESTAMP WITH TIME ZONE', 'TIMESTAMP WITH LOCAL TIME ZONE'): MYSQL_DATA_TYPE.TIMESTAMP,
|
|
48
|
+
('RAW', 'LONG RAW', 'BLOB', 'BFILE'): MYSQL_DATA_TYPE.BINARY,
|
|
49
|
+
('ROWID', 'UROWID'): MYSQL_DATA_TYPE.TEXT,
|
|
50
|
+
('CHAR', 'NCHAR', 'CLOB', 'NCLOB', 'CHARACTER'): MYSQL_DATA_TYPE.CHAR,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for db_types_list, mysql_data_type in types_map.items():
|
|
54
|
+
if internal_type_name in db_types_list:
|
|
55
|
+
return mysql_data_type
|
|
56
|
+
|
|
57
|
+
logger.warning(f"Oracle handler type mapping: unknown type: {internal_type_name}, use VARCHAR as fallback.")
|
|
58
|
+
return MYSQL_DATA_TYPE.VARCHAR
|
|
59
|
+
|
|
60
|
+
|
|
22
61
|
class OracleHandler(DatabaseHandler):
|
|
23
62
|
"""
|
|
24
63
|
This handler handles connection and execution of SQL queries on Oracle.
|
|
@@ -94,6 +133,12 @@ class OracleHandler(DatabaseHandler):
|
|
|
94
133
|
connection = connect(
|
|
95
134
|
**config,
|
|
96
135
|
)
|
|
136
|
+
|
|
137
|
+
if 'session_variables' in self.connection_data:
|
|
138
|
+
with connection.cursor() as cur:
|
|
139
|
+
for key, value in self.connection_data['session_variables'].items():
|
|
140
|
+
cur.execute(f"ALTER SESSION SET {key} = {repr(value)}")
|
|
141
|
+
|
|
97
142
|
except DatabaseError as database_error:
|
|
98
143
|
logger.error(f'Error connecting to Oracle, {database_error}!')
|
|
99
144
|
raise
|
|
@@ -237,10 +282,13 @@ class OracleHandler(DatabaseHandler):
|
|
|
237
282
|
"""
|
|
238
283
|
query = f"""
|
|
239
284
|
SELECT
|
|
240
|
-
column_name,
|
|
241
|
-
data_type
|
|
285
|
+
column_name AS field,
|
|
286
|
+
data_type AS type
|
|
242
287
|
FROM USER_TAB_COLUMNS
|
|
243
288
|
WHERE table_name = '{table_name}'
|
|
244
289
|
"""
|
|
245
290
|
result = self.native_query(query)
|
|
291
|
+
if result.resp_type is RESPONSE_TYPE.TABLE:
|
|
292
|
+
result.data_frame.columns = [name.lower() for name in result.data_frame.columns]
|
|
293
|
+
result.data_frame['mysql_data_type'] = result.data_frame['type'].apply(_map_type)
|
|
246
294
|
return result
|
|
@@ -20,12 +20,44 @@ from mindsdb.integrations.libs.response import (
|
|
|
20
20
|
RESPONSE_TYPE
|
|
21
21
|
)
|
|
22
22
|
import mindsdb.utilities.profiler as profiler
|
|
23
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
23
24
|
|
|
24
25
|
logger = log.getLogger(__name__)
|
|
25
26
|
|
|
26
27
|
SUBSCRIBE_SLEEP_INTERVAL = 1
|
|
27
28
|
|
|
28
29
|
|
|
30
|
+
def _map_type(internal_type_name: str) -> MYSQL_DATA_TYPE:
|
|
31
|
+
"""Map Postgres types to MySQL types.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
internal_type_name (str): The name of the Postgres type to map.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
MYSQL_DATA_TYPE: The MySQL type that corresponds to the Postgres type.
|
|
38
|
+
"""
|
|
39
|
+
internal_type_name = internal_type_name.lower()
|
|
40
|
+
types_map = {
|
|
41
|
+
('smallint', 'integer', 'bigint', 'int', 'smallserial', 'serial', 'bigserial'): MYSQL_DATA_TYPE.INT,
|
|
42
|
+
('real', 'money', 'float'): MYSQL_DATA_TYPE.FLOAT,
|
|
43
|
+
('numeric', 'decimal'): MYSQL_DATA_TYPE.DECIMAL,
|
|
44
|
+
('double precision',): MYSQL_DATA_TYPE.DOUBLE,
|
|
45
|
+
('character varying', 'varchar', 'character', 'char', 'bpchar', 'bpchar', 'text'): MYSQL_DATA_TYPE.TEXT,
|
|
46
|
+
('timestamp', 'timestamp without time zone', 'timestamp with time zone'): MYSQL_DATA_TYPE.DATETIME,
|
|
47
|
+
('date', ): MYSQL_DATA_TYPE.DATE,
|
|
48
|
+
('time', 'time without time zone', 'time with time zone'): MYSQL_DATA_TYPE.TIME,
|
|
49
|
+
('boolean',): MYSQL_DATA_TYPE.BOOL,
|
|
50
|
+
('bytea',): MYSQL_DATA_TYPE.BINARY,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for db_types_list, mysql_data_type in types_map.items():
|
|
54
|
+
if internal_type_name in db_types_list:
|
|
55
|
+
return mysql_data_type
|
|
56
|
+
|
|
57
|
+
logger.warning(f"Postgres handler type mapping: unknown type: {internal_type_name}, use VARCHAR as fallback.")
|
|
58
|
+
return MYSQL_DATA_TYPE.VARCHAR
|
|
59
|
+
|
|
60
|
+
|
|
29
61
|
class PostgresHandler(DatabaseHandler):
|
|
30
62
|
"""
|
|
31
63
|
This handler handles connection and execution of the PostgreSQL statements.
|
|
@@ -314,7 +346,11 @@ class PostgresHandler(DatabaseHandler):
|
|
|
314
346
|
AND
|
|
315
347
|
table_schema = {schema_name}
|
|
316
348
|
"""
|
|
317
|
-
|
|
349
|
+
result = self.native_query(query)
|
|
350
|
+
if result.resp_type is RESPONSE_TYPE.TABLE:
|
|
351
|
+
result.data_frame.columns = [name.lower() for name in result.data_frame.columns]
|
|
352
|
+
result.data_frame['mysql_data_type'] = result.data_frame['type'].apply(_map_type)
|
|
353
|
+
return result
|
|
318
354
|
|
|
319
355
|
def subscribe(self, stop_event, callback, table_name, columns=None, **kwargs):
|
|
320
356
|
config = self._make_connection_args()
|
|
@@ -16,6 +16,7 @@ from mindsdb.integrations.libs.response import (
|
|
|
16
16
|
HandlerResponse as Response,
|
|
17
17
|
RESPONSE_TYPE
|
|
18
18
|
)
|
|
19
|
+
from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
|
|
19
20
|
|
|
20
21
|
try:
|
|
21
22
|
import pyarrow as pa
|
|
@@ -27,6 +28,43 @@ except Exception:
|
|
|
27
28
|
logger = log.getLogger(__name__)
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def _map_type(internal_type_name: str) -> MYSQL_DATA_TYPE:
|
|
32
|
+
""" Map Snowflake types to MySQL types.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
internal_type_name (str): The name of the Snowflake type to map.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
MYSQL_DATA_TYPE: The MySQL type that corresponds to the Snowflake type.
|
|
39
|
+
"""
|
|
40
|
+
internal_type_name = internal_type_name.upper()
|
|
41
|
+
types_map = {
|
|
42
|
+
('NUMBER', 'DECIMAL', 'DEC', 'NUMERIC'): MYSQL_DATA_TYPE.DECIMAL,
|
|
43
|
+
('INT , INTEGER , BIGINT , SMALLINT , TINYINT , BYTEINT'): MYSQL_DATA_TYPE.INT,
|
|
44
|
+
('FLOAT', 'FLOAT4', 'FLOAT8'): MYSQL_DATA_TYPE.FLOAT,
|
|
45
|
+
('DOUBLE', 'DOUBLE PRECISION', 'REAL'): MYSQL_DATA_TYPE.DOUBLE,
|
|
46
|
+
('VARCHAR'): MYSQL_DATA_TYPE.VARCHAR,
|
|
47
|
+
('CHAR', 'CHARACTER', 'NCHAR'): MYSQL_DATA_TYPE.CHAR,
|
|
48
|
+
('STRING', 'TEXT', 'NVARCHAR'): MYSQL_DATA_TYPE.TEXT,
|
|
49
|
+
('NVARCHAR2', 'CHAR VARYING', 'NCHAR VARYING'): MYSQL_DATA_TYPE.VARCHAR,
|
|
50
|
+
('BINARY', 'VARBINARY'): MYSQL_DATA_TYPE.BINARY,
|
|
51
|
+
('BOOLEAN',): MYSQL_DATA_TYPE.BOOL,
|
|
52
|
+
('TIMESTAMP_NTZ', 'DATETIME'): MYSQL_DATA_TYPE.DATETIME,
|
|
53
|
+
('DATE',): MYSQL_DATA_TYPE.DATE,
|
|
54
|
+
('TIME',): MYSQL_DATA_TYPE.TIME,
|
|
55
|
+
('TIMESTAMP_LTZ'): MYSQL_DATA_TYPE.DATETIME,
|
|
56
|
+
('TIMESTAMP_TZ'): MYSQL_DATA_TYPE.DATETIME,
|
|
57
|
+
('VARIANT', 'OBJECT', 'ARRAY', 'MAP', 'GEOGRAPHY', 'GEOMETRY', 'VECTOR'): MYSQL_DATA_TYPE.VARCHAR
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for db_types_list, mysql_data_type in types_map.items():
|
|
61
|
+
if internal_type_name in db_types_list:
|
|
62
|
+
return mysql_data_type
|
|
63
|
+
|
|
64
|
+
logger.warning(f"Snowflake handler type mapping: unknown type: {internal_type_name}, use VARCHAR as fallback.")
|
|
65
|
+
return MYSQL_DATA_TYPE.VARCHAR
|
|
66
|
+
|
|
67
|
+
|
|
30
68
|
class SnowflakeHandler(DatabaseHandler):
|
|
31
69
|
"""
|
|
32
70
|
This handler handles connection and execution of the Snowflake statements.
|
|
@@ -286,6 +324,7 @@ class SnowflakeHandler(DatabaseHandler):
|
|
|
286
324
|
|
|
287
325
|
Returns:
|
|
288
326
|
Response: A response object containing the column details, formatted as per the `Response` class.
|
|
327
|
+
|
|
289
328
|
Raises:
|
|
290
329
|
ValueError: If the 'table_name' is not a valid string.
|
|
291
330
|
"""
|
|
@@ -300,6 +339,8 @@ class SnowflakeHandler(DatabaseHandler):
|
|
|
300
339
|
AND TABLE_SCHEMA = current_schema()
|
|
301
340
|
"""
|
|
302
341
|
result = self.native_query(query)
|
|
303
|
-
result.
|
|
342
|
+
if result.resp_type is RESPONSE_TYPE.TABLE:
|
|
343
|
+
result.data_frame = result.data_frame.rename(columns={'FIELD': 'Field', 'TYPE': 'Type'})
|
|
344
|
+
result.data_frame['mysql_data_type'] = result.data_frame['Type'].apply(_map_type)
|
|
304
345
|
|
|
305
346
|
return result
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from .google import GoogleUserOAuth2Manager, GoogleServiceAccountOAuth2Manager
|
|
2
|
-
from .microsoft import MSGraphAPIDelegatedPermissionsManager
|
|
2
|
+
from .microsoft import MSGraphAPIApplicationPermissionsManager, MSGraphAPIDelegatedPermissionsManager
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from .ms_graph_api_auth_utilities import MSGraphAPIDelegatedPermissionsManager
|
|
1
|
+
from .ms_graph_api_auth_utilities import MSGraphAPIApplicationPermissionsManager, MSGraphAPIDelegatedPermissionsManager
|
mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
1
2
|
from typing import Dict, List, Text
|
|
2
3
|
|
|
3
4
|
from flask import request
|
|
@@ -6,12 +7,13 @@ import msal
|
|
|
6
7
|
from mindsdb.integrations.utilities.handlers.auth_utilities.exceptions import AuthException
|
|
7
8
|
from mindsdb.utilities import log
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
logger = log.getLogger(__name__)
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
class
|
|
14
|
+
class MSGraphAPIPermissionsManager(ABC):
|
|
13
15
|
"""
|
|
14
|
-
The class for managing the delegated permissions for the Microsoft Graph API.
|
|
16
|
+
The base class for managing the delegated permissions for the Microsoft Graph API.
|
|
15
17
|
"""
|
|
16
18
|
def __init__(
|
|
17
19
|
self,
|
|
@@ -20,10 +22,9 @@ class MSGraphAPIDelegatedPermissionsManager:
|
|
|
20
22
|
tenant_id: Text,
|
|
21
23
|
cache: msal.SerializableTokenCache,
|
|
22
24
|
scopes: List = ["https://graph.microsoft.com/.default"],
|
|
23
|
-
code: Text = None,
|
|
24
25
|
) -> None:
|
|
25
26
|
"""
|
|
26
|
-
Initializes the
|
|
27
|
+
Initializes the permissions manager.
|
|
27
28
|
|
|
28
29
|
Args:
|
|
29
30
|
client_id (Text): The client ID of the application registered in Microsoft Entra ID.
|
|
@@ -38,8 +39,68 @@ class MSGraphAPIDelegatedPermissionsManager:
|
|
|
38
39
|
self.tenant_id = tenant_id
|
|
39
40
|
self.cache = cache
|
|
40
41
|
self.scopes = scopes
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
def get_access_token(self) -> Text:
|
|
45
|
+
"""
|
|
46
|
+
Retrieves an access token for the Microsoft Graph API.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Text: The access token for the Microsoft Graph API.
|
|
50
|
+
"""
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def _get_msal_app(self) -> msal.ConfidentialClientApplication:
|
|
54
|
+
"""
|
|
55
|
+
Returns an instance of the MSAL ConfidentialClientApplication.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
msal.ConfidentialClientApplication: An instance of the MSAL ConfidentialClientApplication.
|
|
59
|
+
"""
|
|
60
|
+
return msal.ConfidentialClientApplication(
|
|
61
|
+
self.client_id,
|
|
62
|
+
authority=f"https://login.microsoftonline.com/{self.tenant_id}",
|
|
63
|
+
client_credential=self.client_secret,
|
|
64
|
+
token_cache=self.cache,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class MSGraphAPIDelegatedPermissionsManager(MSGraphAPIPermissionsManager):
|
|
69
|
+
"""
|
|
70
|
+
The class for managing the delegated permissions for the Microsoft Graph API.
|
|
71
|
+
"""
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
client_id: Text,
|
|
75
|
+
client_secret: Text,
|
|
76
|
+
tenant_id: Text,
|
|
77
|
+
cache: msal.SerializableTokenCache,
|
|
78
|
+
scopes: List = ["https://graph.microsoft.com/.default"],
|
|
79
|
+
code: Text = None,
|
|
80
|
+
) -> None:
|
|
81
|
+
"""
|
|
82
|
+
Initializes the delegated permissions manager.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
client_id (Text): The client ID of the application registered in Microsoft Entra ID.
|
|
86
|
+
client_secret (Text): The client secret of the application registered in Microsoft Entra ID.
|
|
87
|
+
tenant_id (Text): The tenant ID of the application registered in Microsoft Entra ID.
|
|
88
|
+
cache (msal.SerializableTokenCache): The token cache for storing the access token.
|
|
89
|
+
scopes (List): The scopes for the Microsoft Graph API.
|
|
90
|
+
code (Text): The authentication code for acquiring the access token.
|
|
91
|
+
"""
|
|
92
|
+
super().__init__(client_id, client_secret, tenant_id, cache, scopes)
|
|
41
93
|
self.code = code
|
|
94
|
+
self.redirect_uri = None
|
|
95
|
+
self._set_redirect_uri()
|
|
96
|
+
|
|
97
|
+
def _set_redirect_uri(self) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Sets the redirect URI based on the request origin.
|
|
42
100
|
|
|
101
|
+
Raises:
|
|
102
|
+
AuthException: If the request origin could not be determined.
|
|
103
|
+
"""
|
|
43
104
|
# Set the redirect URI based on the request origin.
|
|
44
105
|
# If the request origin is 127.0.0.1 (localhost), replace it with localhost.
|
|
45
106
|
# This is done because the only HTTP origin allowed in Microsoft Entra ID app registration is localhost.
|
|
@@ -85,20 +146,6 @@ class MSGraphAPIDelegatedPermissionsManager:
|
|
|
85
146
|
auth_url=response.get('auth_url')
|
|
86
147
|
)
|
|
87
148
|
|
|
88
|
-
def _get_msal_app(self) -> msal.ConfidentialClientApplication:
|
|
89
|
-
"""
|
|
90
|
-
Returns an instance of the MSAL ConfidentialClientApplication.
|
|
91
|
-
|
|
92
|
-
Returns:
|
|
93
|
-
msal.ConfidentialClientApplication: An instance of the MSAL ConfidentialClientApplication.
|
|
94
|
-
"""
|
|
95
|
-
return msal.ConfidentialClientApplication(
|
|
96
|
-
self.client_id,
|
|
97
|
-
authority=f"https://login.microsoftonline.com/{self.tenant_id}",
|
|
98
|
-
client_credential=self.client_secret,
|
|
99
|
-
token_cache=self.cache,
|
|
100
|
-
)
|
|
101
|
-
|
|
102
149
|
def _execute_ms_graph_api_auth_flow(self) -> Dict:
|
|
103
150
|
"""
|
|
104
151
|
Executes the authentication flow for the Microsoft Graph API.
|
|
@@ -131,3 +178,35 @@ class MSGraphAPIDelegatedPermissionsManager:
|
|
|
131
178
|
)
|
|
132
179
|
|
|
133
180
|
raise AuthException(f'Authorisation required. Please follow the url: {auth_url}', auth_url=auth_url)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class MSGraphAPIApplicationPermissionsManager(MSGraphAPIPermissionsManager):
|
|
184
|
+
"""
|
|
185
|
+
The class for managing application permissions for the Microsoft Graph API.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
def get_access_token(self) -> Text:
|
|
189
|
+
"""
|
|
190
|
+
Retrieves an access token for the Microsoft Graph API using the client credentials flow.
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Text: The access token for the Microsoft Graph API.
|
|
194
|
+
"""
|
|
195
|
+
msal_app = self._get_msal_app()
|
|
196
|
+
|
|
197
|
+
# Check if a valid access token is already in the cache.
|
|
198
|
+
accounts = msal_app.get_accounts()
|
|
199
|
+
if accounts:
|
|
200
|
+
response = msal_app.acquire_token_silent(self.scopes, account=accounts[0])
|
|
201
|
+
if "access_token" in response:
|
|
202
|
+
return response["access_token"]
|
|
203
|
+
|
|
204
|
+
# If no valid access token is found in the cache, acquire a new token using client credentials.
|
|
205
|
+
response = msal_app.acquire_token_for_client(scopes=self.scopes)
|
|
206
|
+
|
|
207
|
+
if "access_token" in response:
|
|
208
|
+
return response["access_token"]
|
|
209
|
+
else:
|
|
210
|
+
raise AuthException(
|
|
211
|
+
f"Error getting access token: {response.get('error_description')}"
|
|
212
|
+
)
|
|
@@ -27,6 +27,7 @@ types_map = {}
|
|
|
27
27
|
for type_name in sa_type_names:
|
|
28
28
|
types_map[type_name.upper()] = getattr(sa.types, type_name)
|
|
29
29
|
types_map['BOOL'] = types_map['BOOLEAN']
|
|
30
|
+
types_map['DEC'] = types_map['DECIMAL']
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class RenderError(Exception):
|
|
@@ -43,6 +44,11 @@ class INTERVAL(ColumnElement):
|
|
|
43
44
|
@compiles(INTERVAL)
|
|
44
45
|
def _compile_interval(element, compiler, **kw):
|
|
45
46
|
items = element.info.split(' ', maxsplit=1)
|
|
47
|
+
if compiler.dialect.name == 'oracle' and len(items) == 2:
|
|
48
|
+
# replace to singular names (remove leading S if exists)
|
|
49
|
+
if items[1].upper().endswith('S'):
|
|
50
|
+
items[1] = items[1][:-1]
|
|
51
|
+
|
|
46
52
|
if compiler.dialect.driver in ['snowflake']:
|
|
47
53
|
# quote all
|
|
48
54
|
args = " ".join(map(str, items))
|
|
@@ -118,6 +124,8 @@ class SqlalchemyRender:
|
|
|
118
124
|
self.dialect = dialect(paramstyle="named")
|
|
119
125
|
self.dialect.div_is_floordiv = False
|
|
120
126
|
|
|
127
|
+
self.selects_stack = []
|
|
128
|
+
|
|
121
129
|
if dialect_name == 'mssql':
|
|
122
130
|
# update version to MS_2008_VERSION for supports_multivalues_insert
|
|
123
131
|
self.dialect.server_version_info = (10,)
|
|
@@ -143,8 +151,10 @@ class SqlalchemyRender:
|
|
|
143
151
|
part = self.dialect.identifier_preparer.quote(i)
|
|
144
152
|
|
|
145
153
|
parts2.append(part)
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
text = '.'.join(parts2)
|
|
155
|
+
if identifier.is_outer and self.dialect.name == 'oracle':
|
|
156
|
+
text += '(+)'
|
|
157
|
+
return sa.column(text, is_literal=True)
|
|
148
158
|
|
|
149
159
|
def get_alias(self, alias):
|
|
150
160
|
if alias is None or len(alias.parts) == 0:
|
|
@@ -152,6 +162,9 @@ class SqlalchemyRender:
|
|
|
152
162
|
if len(alias.parts) > 1:
|
|
153
163
|
raise NotImplementedError(f'Multiple alias {alias.parts}')
|
|
154
164
|
|
|
165
|
+
if self.selects_stack:
|
|
166
|
+
self.selects_stack[-1]['aliases'].append(alias)
|
|
167
|
+
|
|
155
168
|
is_quoted = get_is_quoted(alias)[0]
|
|
156
169
|
return AttributedStr(alias.parts[0], is_quoted)
|
|
157
170
|
|
|
@@ -205,12 +218,18 @@ class SqlalchemyRender:
|
|
|
205
218
|
alias = self.get_alias(t.alias)
|
|
206
219
|
col = col.label(alias)
|
|
207
220
|
elif isinstance(t, ast.Function):
|
|
208
|
-
|
|
221
|
+
col = self.to_function(t)
|
|
209
222
|
if t.alias:
|
|
210
223
|
alias = self.get_alias(t.alias)
|
|
224
|
+
col = col.label(alias)
|
|
211
225
|
else:
|
|
212
226
|
alias = str(t.op)
|
|
213
|
-
|
|
227
|
+
if self.selects_stack:
|
|
228
|
+
aliases = self.selects_stack[-1]['aliases']
|
|
229
|
+
if alias not in aliases:
|
|
230
|
+
aliases.append(alias)
|
|
231
|
+
col = col.label(alias)
|
|
232
|
+
|
|
214
233
|
elif isinstance(t, ast.BinaryOperation):
|
|
215
234
|
ops = {
|
|
216
235
|
"+": operators.add,
|
|
@@ -432,9 +451,9 @@ class SqlalchemyRender:
|
|
|
432
451
|
return typename
|
|
433
452
|
|
|
434
453
|
typename = typename.upper()
|
|
435
|
-
if re.match(r'^INT[\d]
|
|
454
|
+
if re.match(r'^INT[\d]+$', typename):
|
|
436
455
|
typename = 'BIGINT'
|
|
437
|
-
if re.match(r'^FLOAT[\d]
|
|
456
|
+
if re.match(r'^FLOAT[\d]+$', typename):
|
|
438
457
|
typename = 'FLOAT'
|
|
439
458
|
|
|
440
459
|
return types_map[typename]
|
|
@@ -513,6 +532,9 @@ class SqlalchemyRender:
|
|
|
513
532
|
return self.prepare_union(node)
|
|
514
533
|
|
|
515
534
|
cols = []
|
|
535
|
+
|
|
536
|
+
self.selects_stack.append({'aliases': []})
|
|
537
|
+
|
|
516
538
|
for t in node.targets:
|
|
517
539
|
col = self.to_expression(t)
|
|
518
540
|
cols.append(col)
|
|
@@ -647,6 +669,8 @@ class SqlalchemyRender:
|
|
|
647
669
|
else:
|
|
648
670
|
raise NotImplementedError(f'Select mode: {node.mode}')
|
|
649
671
|
|
|
672
|
+
self.selects_stack.pop()
|
|
673
|
+
|
|
650
674
|
return query
|
|
651
675
|
|
|
652
676
|
def prepare_union(self, from_table):
|