mssql-python 1.0.0__cp314-cp314-win_arm64.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.
Files changed (72) hide show
  1. mssql_python/__init__.py +307 -0
  2. mssql_python/auth.py +222 -0
  3. mssql_python/connection.py +1493 -0
  4. mssql_python/connection_string_builder.py +113 -0
  5. mssql_python/connection_string_parser.py +381 -0
  6. mssql_python/constants.py +509 -0
  7. mssql_python/cursor.py +2601 -0
  8. mssql_python/db_connection.py +53 -0
  9. mssql_python/ddbc_bindings.cp314-arm64.pyd +0 -0
  10. mssql_python/ddbc_bindings.py +137 -0
  11. mssql_python/exceptions.py +574 -0
  12. mssql_python/helpers.py +357 -0
  13. mssql_python/libs/LICENSING +19 -0
  14. mssql_python/libs/linux/alpine/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  15. mssql_python/libs/linux/alpine/arm64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  16. mssql_python/libs/linux/alpine/arm64/lib/libodbcinst.so.2 +0 -0
  17. mssql_python/libs/linux/alpine/arm64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  18. mssql_python/libs/linux/alpine/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  19. mssql_python/libs/linux/alpine/x86_64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  20. mssql_python/libs/linux/alpine/x86_64/lib/libodbcinst.so.2 +0 -0
  21. mssql_python/libs/linux/alpine/x86_64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  22. mssql_python/libs/linux/debian_ubuntu/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  23. mssql_python/libs/linux/debian_ubuntu/arm64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  24. mssql_python/libs/linux/debian_ubuntu/arm64/lib/libodbcinst.so.2 +0 -0
  25. mssql_python/libs/linux/debian_ubuntu/arm64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  26. mssql_python/libs/linux/debian_ubuntu/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  27. mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  28. mssql_python/libs/linux/debian_ubuntu/x86_64/lib/libodbcinst.so.2 +0 -0
  29. mssql_python/libs/linux/debian_ubuntu/x86_64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  30. mssql_python/libs/linux/rhel/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  31. mssql_python/libs/linux/rhel/arm64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  32. mssql_python/libs/linux/rhel/arm64/lib/libodbcinst.so.2 +0 -0
  33. mssql_python/libs/linux/rhel/arm64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  34. mssql_python/libs/linux/rhel/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  35. mssql_python/libs/linux/rhel/x86_64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  36. mssql_python/libs/linux/rhel/x86_64/lib/libodbcinst.so.2 +0 -0
  37. mssql_python/libs/linux/rhel/x86_64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  38. mssql_python/libs/linux/suse/x86_64/lib/libmsodbcsql-18.5.so.1.1 +0 -0
  39. mssql_python/libs/linux/suse/x86_64/lib/libodbcinst.so.2 +0 -0
  40. mssql_python/libs/linux/suse/x86_64/share/resources/en_US/msodbcsqlr18.rll +0 -0
  41. mssql_python/libs/macos/arm64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  42. mssql_python/libs/macos/arm64/lib/libltdl.7.dylib +0 -0
  43. mssql_python/libs/macos/arm64/lib/libmsodbcsql.18.dylib +0 -0
  44. mssql_python/libs/macos/arm64/lib/libodbc.2.dylib +0 -0
  45. mssql_python/libs/macos/arm64/lib/libodbcinst.2.dylib +0 -0
  46. mssql_python/libs/macos/arm64/share/doc/msodbcsql18/LICENSE.txt +101 -0
  47. mssql_python/libs/macos/arm64/share/doc/msodbcsql18/RELEASE_NOTES +465 -0
  48. mssql_python/libs/macos/arm64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll +0 -0
  49. mssql_python/libs/macos/x86_64/lib/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  50. mssql_python/libs/macos/x86_64/lib/libltdl.7.dylib +0 -0
  51. mssql_python/libs/macos/x86_64/lib/libmsodbcsql.18.dylib +0 -0
  52. mssql_python/libs/macos/x86_64/lib/libodbc.2.dylib +0 -0
  53. mssql_python/libs/macos/x86_64/lib/libodbcinst.2.dylib +0 -0
  54. mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/LICENSE.txt +101 -0
  55. mssql_python/libs/macos/x86_64/share/doc/msodbcsql18/RELEASE_NOTES +465 -0
  56. mssql_python/libs/macos/x86_64/share/msodbcsql18/resources/en_US/msodbcsqlr18.rll +0 -0
  57. mssql_python/libs/windows/arm64/1033/msodbcsqlr18.rll +0 -0
  58. mssql_python/libs/windows/arm64/MICROSOFT_ODBC_DRIVER_FOR_SQL_SERVER_LICENSE.txt +76 -0
  59. mssql_python/libs/windows/arm64/msodbcdiag18.dll +0 -0
  60. mssql_python/libs/windows/arm64/msodbcsql18.dll +0 -0
  61. mssql_python/libs/windows/arm64/mssql-auth.dll +0 -0
  62. mssql_python/logging.py +610 -0
  63. mssql_python/mssql_python.pyi +375 -0
  64. mssql_python/msvcp140.dll +0 -0
  65. mssql_python/pooling.py +125 -0
  66. mssql_python/row.py +194 -0
  67. mssql_python/type.py +143 -0
  68. mssql_python-1.0.0.dist-info/METADATA +76 -0
  69. mssql_python-1.0.0.dist-info/RECORD +72 -0
  70. mssql_python-1.0.0.dist-info/WHEEL +5 -0
  71. mssql_python-1.0.0.dist-info/licenses/LICENSE +47 -0
  72. mssql_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,307 @@
1
+ """
2
+ Copyright (c) Microsoft Corporation.
3
+ Licensed under the MIT license.
4
+ This module initializes the mssql_python package.
5
+ """
6
+ import sys
7
+ import types
8
+ from typing import Dict
9
+
10
+ # Import settings from helpers to avoid circular imports
11
+ from .helpers import Settings, get_settings, _settings, _settings_lock
12
+
13
+ # Driver version
14
+ __version__ = "1.0.0"
15
+
16
+ # Exceptions
17
+ # https://www.python.org/dev/peps/pep-0249/#exceptions
18
+
19
+ # Import necessary modules
20
+ from .exceptions import (
21
+ Warning,
22
+ Error,
23
+ InterfaceError,
24
+ DatabaseError,
25
+ DataError,
26
+ OperationalError,
27
+ IntegrityError,
28
+ InternalError,
29
+ ProgrammingError,
30
+ NotSupportedError,
31
+ ConnectionStringParseError,
32
+ )
33
+
34
+ # Type Objects
35
+ from .type import (
36
+ Date,
37
+ Time,
38
+ Timestamp,
39
+ DateFromTicks,
40
+ TimeFromTicks,
41
+ TimestampFromTicks,
42
+ Binary,
43
+ STRING,
44
+ BINARY,
45
+ NUMBER,
46
+ DATETIME,
47
+ ROWID,
48
+ )
49
+
50
+ # Connection Objects
51
+ from .db_connection import connect, Connection
52
+
53
+ # Connection String Handling
54
+ from .connection_string_parser import _ConnectionStringParser
55
+ from .connection_string_builder import _ConnectionStringBuilder
56
+
57
+ # Cursor Objects
58
+ from .cursor import Cursor
59
+
60
+ # Logging Configuration (Simplified single-level DEBUG system)
61
+ from .logging import logger, setup_logging, driver_logger
62
+
63
+ # Constants
64
+ from .constants import ConstantsDDBC, GetInfoConstants
65
+
66
+ # Pooling
67
+ from .pooling import PoolingManager
68
+
69
+ # GLOBALS
70
+ # Read-Only
71
+ apilevel: str = "2.0"
72
+ paramstyle: str = "qmark"
73
+ threadsafety: int = 1
74
+
75
+ # Set the initial decimal separator in C++
76
+ try:
77
+ from .ddbc_bindings import DDBCSetDecimalSeparator
78
+ DDBCSetDecimalSeparator(_settings.decimal_separator)
79
+ except ImportError:
80
+ # Handle case where ddbc_bindings is not available
81
+ DDBCSetDecimalSeparator = None
82
+
83
+
84
+ # New functions for decimal separator control
85
+ def setDecimalSeparator(separator: str) -> None:
86
+ """
87
+ Sets the decimal separator character used when parsing NUMERIC/DECIMAL values
88
+ from the database, e.g. the "." in "1,234.56".
89
+
90
+ The default is to use the current locale's "decimal_point" value when the module
91
+ was first imported, or "." if the locale is not available. This function overrides
92
+ the default.
93
+
94
+ Args:
95
+ separator (str): The character to use as decimal separator
96
+
97
+ Raises:
98
+ ValueError: If the separator is not a single character string
99
+ """
100
+ # Type validation
101
+ if not isinstance(separator, str):
102
+ raise ValueError("Decimal separator must be a string")
103
+
104
+ # Length validation
105
+ if len(separator) == 0:
106
+ raise ValueError("Decimal separator cannot be empty")
107
+
108
+ if len(separator) > 1:
109
+ raise ValueError("Decimal separator must be a single character")
110
+
111
+ # Character validation
112
+ if separator.isspace():
113
+ raise ValueError("Whitespace characters are not allowed as decimal separators")
114
+
115
+ # Check for specific disallowed characters
116
+ if separator in ["\t", "\n", "\r", "\v", "\f"]:
117
+ raise ValueError(
118
+ f"Control character '{repr(separator)}' is not allowed as a decimal separator"
119
+ )
120
+
121
+ # Set in Python side settings
122
+ _settings.decimal_separator = separator
123
+
124
+ # Update the C++ side
125
+ if DDBCSetDecimalSeparator is not None:
126
+ DDBCSetDecimalSeparator(separator)
127
+
128
+
129
+ def getDecimalSeparator() -> str:
130
+ """
131
+ Returns the decimal separator character used when parsing NUMERIC/DECIMAL values
132
+ from the database.
133
+
134
+ Returns:
135
+ str: The current decimal separator character
136
+ """
137
+ return _settings.decimal_separator
138
+
139
+
140
+ # Export specific constants for setencoding()
141
+ SQL_CHAR: int = ConstantsDDBC.SQL_CHAR.value
142
+ SQL_WCHAR: int = ConstantsDDBC.SQL_WCHAR.value
143
+ SQL_WMETADATA: int = -99
144
+
145
+ # Export connection attribute constants for set_attr()
146
+ # Only include driver-level attributes that the SQL Server ODBC driver can handle directly
147
+
148
+ # Core driver-level attributes
149
+ SQL_ATTR_ACCESS_MODE: int = ConstantsDDBC.SQL_ATTR_ACCESS_MODE.value
150
+ SQL_ATTR_CONNECTION_TIMEOUT: int = ConstantsDDBC.SQL_ATTR_CONNECTION_TIMEOUT.value
151
+ SQL_ATTR_CURRENT_CATALOG: int = ConstantsDDBC.SQL_ATTR_CURRENT_CATALOG.value
152
+ SQL_ATTR_LOGIN_TIMEOUT: int = ConstantsDDBC.SQL_ATTR_LOGIN_TIMEOUT.value
153
+ SQL_ATTR_PACKET_SIZE: int = ConstantsDDBC.SQL_ATTR_PACKET_SIZE.value
154
+ SQL_ATTR_TXN_ISOLATION: int = ConstantsDDBC.SQL_ATTR_TXN_ISOLATION.value
155
+
156
+ # Transaction Isolation Level Constants
157
+ SQL_TXN_READ_UNCOMMITTED: int = ConstantsDDBC.SQL_TXN_READ_UNCOMMITTED.value
158
+ SQL_TXN_READ_COMMITTED: int = ConstantsDDBC.SQL_TXN_READ_COMMITTED.value
159
+ SQL_TXN_REPEATABLE_READ: int = ConstantsDDBC.SQL_TXN_REPEATABLE_READ.value
160
+ SQL_TXN_SERIALIZABLE: int = ConstantsDDBC.SQL_TXN_SERIALIZABLE.value
161
+
162
+ # Access Mode Constants
163
+ SQL_MODE_READ_WRITE: int = ConstantsDDBC.SQL_MODE_READ_WRITE.value
164
+ SQL_MODE_READ_ONLY: int = ConstantsDDBC.SQL_MODE_READ_ONLY.value
165
+
166
+
167
+ def pooling(max_size: int = 100, idle_timeout: int = 600, enabled: bool = True) -> None:
168
+ """
169
+ Enable connection pooling with the specified parameters.
170
+ By default:
171
+ - If not explicitly called, pooling will be auto-enabled with default values.
172
+
173
+ Args:
174
+ max_size (int): Maximum number of connections in the pool.
175
+ idle_timeout (int): Time in seconds before idle connections are closed.
176
+ enabled (bool): Whether to enable or disable pooling.
177
+
178
+ Returns:
179
+ None
180
+ """
181
+ if not enabled:
182
+ PoolingManager.disable()
183
+ else:
184
+ PoolingManager.enable(max_size, idle_timeout)
185
+
186
+ _original_module_setattr = sys.modules[__name__].__setattr__
187
+
188
+ def _custom_setattr(name, value):
189
+ if name == 'lowercase':
190
+ with _settings_lock:
191
+ _settings.lowercase = bool(value)
192
+ # Update the module's lowercase variable
193
+ _original_module_setattr(name, _settings.lowercase)
194
+ else:
195
+ _original_module_setattr(name, value)
196
+
197
+ # Replace the module's __setattr__ with our custom version
198
+ sys.modules[__name__].__setattr__ = _custom_setattr
199
+
200
+
201
+ # Export SQL constants at module level
202
+ SQL_VARCHAR: int = ConstantsDDBC.SQL_VARCHAR.value
203
+ SQL_LONGVARCHAR: int = ConstantsDDBC.SQL_LONGVARCHAR.value
204
+ SQL_WVARCHAR: int = ConstantsDDBC.SQL_WVARCHAR.value
205
+ SQL_WLONGVARCHAR: int = ConstantsDDBC.SQL_WLONGVARCHAR.value
206
+ SQL_DECIMAL: int = ConstantsDDBC.SQL_DECIMAL.value
207
+ SQL_NUMERIC: int = ConstantsDDBC.SQL_NUMERIC.value
208
+ SQL_BIT: int = ConstantsDDBC.SQL_BIT.value
209
+ SQL_TINYINT: int = ConstantsDDBC.SQL_TINYINT.value
210
+ SQL_SMALLINT: int = ConstantsDDBC.SQL_SMALLINT.value
211
+ SQL_INTEGER: int = ConstantsDDBC.SQL_INTEGER.value
212
+ SQL_BIGINT: int = ConstantsDDBC.SQL_BIGINT.value
213
+ SQL_REAL: int = ConstantsDDBC.SQL_REAL.value
214
+ SQL_FLOAT: int = ConstantsDDBC.SQL_FLOAT.value
215
+ SQL_DOUBLE: int = ConstantsDDBC.SQL_DOUBLE.value
216
+ SQL_BINARY: int = ConstantsDDBC.SQL_BINARY.value
217
+ SQL_VARBINARY: int = ConstantsDDBC.SQL_VARBINARY.value
218
+ SQL_LONGVARBINARY: int = ConstantsDDBC.SQL_LONGVARBINARY.value
219
+ SQL_DATE: int = ConstantsDDBC.SQL_DATE.value
220
+ SQL_TIME: int = ConstantsDDBC.SQL_TIME.value
221
+ SQL_TIMESTAMP: int = ConstantsDDBC.SQL_TIMESTAMP.value
222
+
223
+ # Export GetInfo constants at module level
224
+ # Driver and database information
225
+ SQL_DRIVER_NAME: int = GetInfoConstants.SQL_DRIVER_NAME.value
226
+ SQL_DRIVER_VER: int = GetInfoConstants.SQL_DRIVER_VER.value
227
+ SQL_DRIVER_ODBC_VER: int = GetInfoConstants.SQL_DRIVER_ODBC_VER.value
228
+ SQL_DATA_SOURCE_NAME: int = GetInfoConstants.SQL_DATA_SOURCE_NAME.value
229
+ SQL_DATABASE_NAME: int = GetInfoConstants.SQL_DATABASE_NAME.value
230
+ SQL_SERVER_NAME: int = GetInfoConstants.SQL_SERVER_NAME.value
231
+ SQL_USER_NAME: int = GetInfoConstants.SQL_USER_NAME.value
232
+
233
+ # SQL conformance and support
234
+ SQL_SQL_CONFORMANCE: int = GetInfoConstants.SQL_SQL_CONFORMANCE.value
235
+ SQL_KEYWORDS: int = GetInfoConstants.SQL_KEYWORDS.value
236
+ SQL_IDENTIFIER_QUOTE_CHAR: int = GetInfoConstants.SQL_IDENTIFIER_QUOTE_CHAR.value
237
+ SQL_SEARCH_PATTERN_ESCAPE: int = GetInfoConstants.SQL_SEARCH_PATTERN_ESCAPE.value
238
+
239
+ # Catalog and schema support
240
+ SQL_CATALOG_TERM: int = GetInfoConstants.SQL_CATALOG_TERM.value
241
+ SQL_SCHEMA_TERM: int = GetInfoConstants.SQL_SCHEMA_TERM.value
242
+ SQL_TABLE_TERM: int = GetInfoConstants.SQL_TABLE_TERM.value
243
+ SQL_PROCEDURE_TERM: int = GetInfoConstants.SQL_PROCEDURE_TERM.value
244
+
245
+ # Transaction support
246
+ SQL_TXN_CAPABLE: int = GetInfoConstants.SQL_TXN_CAPABLE.value
247
+ SQL_DEFAULT_TXN_ISOLATION: int = GetInfoConstants.SQL_DEFAULT_TXN_ISOLATION.value
248
+
249
+ # Data type support
250
+ SQL_NUMERIC_FUNCTIONS: int = GetInfoConstants.SQL_NUMERIC_FUNCTIONS.value
251
+ SQL_STRING_FUNCTIONS: int = GetInfoConstants.SQL_STRING_FUNCTIONS.value
252
+ SQL_DATETIME_FUNCTIONS: int = GetInfoConstants.SQL_DATETIME_FUNCTIONS.value
253
+
254
+ # Limits
255
+ SQL_MAX_COLUMN_NAME_LEN: int = GetInfoConstants.SQL_MAX_COLUMN_NAME_LEN.value
256
+ SQL_MAX_TABLE_NAME_LEN: int = GetInfoConstants.SQL_MAX_TABLE_NAME_LEN.value
257
+ SQL_MAX_SCHEMA_NAME_LEN: int = GetInfoConstants.SQL_MAX_SCHEMA_NAME_LEN.value
258
+ SQL_MAX_CATALOG_NAME_LEN: int = GetInfoConstants.SQL_MAX_CATALOG_NAME_LEN.value
259
+ SQL_MAX_IDENTIFIER_LEN: int = GetInfoConstants.SQL_MAX_IDENTIFIER_LEN.value
260
+
261
+
262
+ # Also provide a function to get all constants
263
+ def get_info_constants() -> Dict[str, int]:
264
+ """
265
+ Returns a dictionary of all available GetInfo constants.
266
+
267
+ This provides all SQLGetInfo constants that can be used with the Connection.getinfo() method
268
+ to retrieve metadata about the database server and driver.
269
+
270
+ Returns:
271
+ dict: Dictionary mapping constant names to their integer values
272
+ """
273
+ return {name: member.value for name, member in GetInfoConstants.__members__.items()}
274
+
275
+ # Create a custom module class that uses properties instead of __setattr__
276
+ class _MSSQLModule(types.ModuleType):
277
+ @property
278
+ def lowercase(self) -> bool:
279
+ """Get the lowercase setting."""
280
+ return _settings.lowercase
281
+
282
+ @lowercase.setter
283
+ def lowercase(self, value: bool) -> None:
284
+ """Set the lowercase setting."""
285
+ if not isinstance(value, bool):
286
+ raise ValueError("lowercase must be a boolean value")
287
+ with _settings_lock:
288
+ _settings.lowercase = value
289
+
290
+
291
+ # Replace the current module with our custom module class
292
+ old_module: types.ModuleType = sys.modules[__name__]
293
+ new_module: _MSSQLModule = _MSSQLModule(__name__)
294
+
295
+ # Copy all existing attributes to the new module
296
+ for attr_name in dir(old_module):
297
+ if attr_name != "__class__":
298
+ try:
299
+ setattr(new_module, attr_name, getattr(old_module, attr_name))
300
+ except AttributeError:
301
+ pass
302
+
303
+ # Replace the module in sys.modules
304
+ sys.modules[__name__] = new_module
305
+
306
+ # Initialize property values
307
+ lowercase: bool = _settings.lowercase
mssql_python/auth.py ADDED
@@ -0,0 +1,222 @@
1
+ """
2
+ Copyright (c) Microsoft Corporation.
3
+ Licensed under the MIT license.
4
+ This module handles authentication for the mssql_python package.
5
+ """
6
+
7
+ import platform
8
+ import struct
9
+ from typing import Tuple, Dict, Optional, List
10
+
11
+ from mssql_python.logging import logger
12
+ from mssql_python.constants import AuthType
13
+
14
+
15
+ class AADAuth:
16
+ """Handles Azure Active Directory authentication"""
17
+
18
+ @staticmethod
19
+ def get_token_struct(token: str) -> bytes:
20
+ """Convert token to SQL Server compatible format"""
21
+ logger.debug('get_token_struct: Converting token to SQL Server format - token_length=%d chars', len(token))
22
+ token_bytes = token.encode("UTF-16-LE")
23
+ logger.debug('get_token_struct: Token encoded to UTF-16-LE - byte_length=%d', len(token_bytes))
24
+ return struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)
25
+
26
+ @staticmethod
27
+ def get_token(auth_type: str) -> bytes:
28
+ """Get token using the specified authentication type"""
29
+ # Import Azure libraries inside method to support test mocking
30
+ # pylint: disable=import-outside-toplevel
31
+ try:
32
+ from azure.identity import (
33
+ DefaultAzureCredential,
34
+ DeviceCodeCredential,
35
+ InteractiveBrowserCredential,
36
+ )
37
+ from azure.core.exceptions import ClientAuthenticationError
38
+ except ImportError as e:
39
+ raise RuntimeError(
40
+ "Azure authentication libraries are not installed. "
41
+ "Please install with: pip install azure-identity azure-core"
42
+ ) from e
43
+
44
+ # Mapping of auth types to credential classes
45
+ credential_map = {
46
+ "default": DefaultAzureCredential,
47
+ "devicecode": DeviceCodeCredential,
48
+ "interactive": InteractiveBrowserCredential,
49
+ }
50
+
51
+ credential_class = credential_map[auth_type]
52
+ logger.info('get_token: Starting Azure AD authentication - auth_type=%s, credential_class=%s',
53
+ auth_type, credential_class.__name__)
54
+
55
+ try:
56
+ logger.debug('get_token: Creating credential instance - credential_class=%s', credential_class.__name__)
57
+ credential = credential_class()
58
+ logger.debug('get_token: Requesting token from Azure AD - scope=https://database.windows.net/.default')
59
+ token = credential.get_token("https://database.windows.net/.default").token
60
+ logger.info('get_token: Azure AD token acquired successfully - token_length=%d chars', len(token))
61
+ return AADAuth.get_token_struct(token)
62
+ except ClientAuthenticationError as e:
63
+ # Re-raise with more specific context about Azure AD authentication failure
64
+ logger.error('get_token: Azure AD authentication failed - credential_class=%s, error=%s',
65
+ credential_class.__name__, str(e))
66
+ raise RuntimeError(
67
+ f"Azure AD authentication failed for {credential_class.__name__}: {e}. "
68
+ f"This could be due to invalid credentials, missing environment variables, "
69
+ f"user cancellation, network issues, or unsupported configuration."
70
+ ) from e
71
+ except Exception as e:
72
+ # Catch any other unexpected exceptions
73
+ logger.error('get_token: Unexpected error during credential creation - credential_class=%s, error=%s',
74
+ credential_class.__name__, str(e))
75
+ raise RuntimeError(
76
+ f"Failed to create {credential_class.__name__}: {e}"
77
+ ) from e
78
+
79
+
80
+ def process_auth_parameters(parameters: List[str]) -> Tuple[List[str], Optional[str]]:
81
+ """
82
+ Process connection parameters and extract authentication type.
83
+
84
+ Args:
85
+ parameters: List of connection string parameters
86
+
87
+ Returns:
88
+ Tuple[list, Optional[str]]: Modified parameters and authentication type
89
+
90
+ Raises:
91
+ ValueError: If an invalid authentication type is provided
92
+ """
93
+ logger.debug('process_auth_parameters: Processing %d connection parameters', len(parameters))
94
+ modified_parameters = []
95
+ auth_type = None
96
+
97
+ for param in parameters:
98
+ param = param.strip()
99
+ if not param:
100
+ continue
101
+
102
+ if "=" not in param:
103
+ modified_parameters.append(param)
104
+ continue
105
+
106
+ key, value = param.split("=", 1)
107
+ key_lower = key.lower()
108
+ value_lower = value.lower()
109
+
110
+ if key_lower == "authentication":
111
+ # Check for supported authentication types and set auth_type accordingly
112
+ if value_lower == AuthType.INTERACTIVE.value:
113
+ auth_type = "interactive"
114
+ logger.debug('process_auth_parameters: Interactive authentication detected')
115
+ # Interactive authentication (browser-based); only append parameter for non-Windows
116
+ if platform.system().lower() == "windows":
117
+ logger.debug('process_auth_parameters: Windows platform - using native AADInteractive')
118
+ auth_type = None # Let Windows handle AADInteractive natively
119
+
120
+ elif value_lower == AuthType.DEVICE_CODE.value:
121
+ # Device code authentication (for devices without browser)
122
+ logger.debug('process_auth_parameters: Device code authentication detected')
123
+ auth_type = "devicecode"
124
+ elif value_lower == AuthType.DEFAULT.value:
125
+ # Default authentication (uses DefaultAzureCredential)
126
+ logger.debug('process_auth_parameters: Default Azure authentication detected')
127
+ auth_type = "default"
128
+ modified_parameters.append(param)
129
+
130
+ logger.debug('process_auth_parameters: Processing complete - auth_type=%s, param_count=%d',
131
+ auth_type, len(modified_parameters))
132
+ return modified_parameters, auth_type
133
+
134
+
135
+ def remove_sensitive_params(parameters: List[str]) -> List[str]:
136
+ """Remove sensitive parameters from connection string"""
137
+ logger.debug('remove_sensitive_params: Removing sensitive parameters - input_count=%d', len(parameters))
138
+ exclude_keys = [
139
+ "uid=",
140
+ "pwd=",
141
+ "encrypt=",
142
+ "trustservercertificate=",
143
+ "authentication=",
144
+ ]
145
+ result = [
146
+ param
147
+ for param in parameters
148
+ if not any(param.lower().startswith(exclude) for exclude in exclude_keys)
149
+ ]
150
+ logger.debug('remove_sensitive_params: Sensitive parameters removed - output_count=%d', len(result))
151
+ return result
152
+
153
+
154
+ def get_auth_token(auth_type: str) -> Optional[bytes]:
155
+ """Get authentication token based on auth type"""
156
+ logger.debug('get_auth_token: Starting - auth_type=%s', auth_type)
157
+ if not auth_type:
158
+ logger.debug('get_auth_token: No auth_type specified, returning None')
159
+ return None
160
+
161
+ # Handle platform-specific logic for interactive auth
162
+ if auth_type == "interactive" and platform.system().lower() == "windows":
163
+ logger.debug('get_auth_token: Windows interactive auth - delegating to native handler')
164
+ return None # Let Windows handle AADInteractive natively
165
+
166
+ try:
167
+ token = AADAuth.get_token(auth_type)
168
+ logger.info('get_auth_token: Token acquired successfully - auth_type=%s', auth_type)
169
+ return token
170
+ except (ValueError, RuntimeError) as e:
171
+ logger.warning('get_auth_token: Token acquisition failed - auth_type=%s, error=%s', auth_type, str(e))
172
+ return None
173
+
174
+
175
+ def process_connection_string(
176
+ connection_string: str,
177
+ ) -> Tuple[str, Optional[Dict[int, bytes]]]:
178
+ """
179
+ Process connection string and handle authentication.
180
+
181
+ Args:
182
+ connection_string: The connection string to process
183
+
184
+ Returns:
185
+ Tuple[str, Optional[Dict]]: Processed connection string and attrs_before dict if needed
186
+
187
+ Raises:
188
+ ValueError: If the connection string is invalid or empty
189
+ """
190
+ logger.debug('process_connection_string: Starting - conn_str_length=%d', len(connection_string) if isinstance(connection_string, str) else 0)
191
+ # Check type first
192
+ if not isinstance(connection_string, str):
193
+ logger.error('process_connection_string: Invalid type - expected str, got %s', type(connection_string).__name__)
194
+ raise ValueError("Connection string must be a string")
195
+
196
+ # Then check if empty
197
+ if not connection_string:
198
+ logger.error('process_connection_string: Connection string is empty')
199
+ raise ValueError("Connection string cannot be empty")
200
+
201
+ parameters = connection_string.split(";")
202
+ logger.debug('process_connection_string: Split connection string - parameter_count=%d', len(parameters))
203
+
204
+ # Validate that there's at least one valid parameter
205
+ if not any("=" in param for param in parameters):
206
+ logger.error('process_connection_string: Invalid connection string format - no key=value pairs found')
207
+ raise ValueError("Invalid connection string format")
208
+
209
+ modified_parameters, auth_type = process_auth_parameters(parameters)
210
+
211
+ if auth_type:
212
+ logger.info('process_connection_string: Authentication type detected - auth_type=%s', auth_type)
213
+ modified_parameters = remove_sensitive_params(modified_parameters)
214
+ token_struct = get_auth_token(auth_type)
215
+ if token_struct:
216
+ logger.info('process_connection_string: Token authentication configured successfully - auth_type=%s', auth_type)
217
+ return ";".join(modified_parameters) + ";", {1256: token_struct}
218
+ else:
219
+ logger.warning('process_connection_string: Token acquisition failed, proceeding without token')
220
+
221
+ logger.debug('process_connection_string: Connection string processing complete - has_auth=%s', bool(auth_type))
222
+ return ";".join(modified_parameters) + ";", None