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.

Files changed (22) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -2
  3. mindsdb/api/executor/datahub/datanodes/system_tables.py +131 -138
  4. mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +74 -0
  5. mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +14 -2
  6. mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -55
  7. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +52 -21
  8. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +6 -29
  9. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +37 -1
  10. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +28 -1
  11. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +53 -5
  12. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +37 -1
  13. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +42 -1
  14. mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +1 -1
  15. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/__init__.py +1 -1
  16. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +97 -18
  17. mindsdb/utilities/render/sqlalchemy_render.py +30 -6
  18. {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/METADATA +226 -228
  19. {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/RECORD +22 -22
  20. {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/WHEEL +0 -0
  21. {mindsdb-25.3.4.1.dist-info → mindsdb-25.3.4.2.dist-info}/licenses/LICENSE +0 -0
  22. {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
- return self.native_query(query)
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"DESCRIBE `{table_name}`;"
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
- return self.native_query(query)
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.data_frame = result.data_frame.rename(columns={'FIELD': 'Field', 'TYPE': 'Type'})
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
@@ -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 MSGraphAPIDelegatedPermissionsManager:
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 delegated permissions manager.
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
- return sa.column('.'.join(parts2), is_literal=True)
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
- fnc = self.to_function(t)
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
- col = fnc.label(alias)
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]*$', typename):
454
+ if re.match(r'^INT[\d]+$', typename):
436
455
  typename = 'BIGINT'
437
- if re.match(r'^FLOAT[\d]*$', typename):
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):