mssql-python 1.16.0__cp315-cp315-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.
- mssql_python/__init__.py +662 -0
- mssql_python/async_query/__init__.py +40 -0
- mssql_python/async_query/_connection_context.py +39 -0
- mssql_python/async_query/_native.py +23 -0
- mssql_python/async_query/async_connection.py +160 -0
- mssql_python/async_query/async_cursor.py +230 -0
- mssql_python/async_query/async_execute.py +115 -0
- mssql_python/async_query/async_fetch.py +134 -0
- mssql_python/async_query/exception_translator.py +114 -0
- mssql_python/auth.py +913 -0
- mssql_python/connection.py +2357 -0
- mssql_python/connection_string_builder.py +114 -0
- mssql_python/connection_string_parser.py +424 -0
- mssql_python/constants.py +701 -0
- mssql_python/cursor.py +4188 -0
- mssql_python/db_connection.py +98 -0
- mssql_python/ddbc_bindings.cp315-arm64.pyd +0 -0
- mssql_python/ddbc_bindings.py +215 -0
- mssql_python/decimal_config.py +114 -0
- mssql_python/exceptions.py +588 -0
- mssql_python/helpers.py +416 -0
- mssql_python/logging.py +714 -0
- mssql_python/mssql_python.pyi +481 -0
- mssql_python/msvcp140.dll +0 -0
- mssql_python/odbc_provider.py +251 -0
- mssql_python/parameter_helper.py +488 -0
- mssql_python/perf_timer.py +286 -0
- mssql_python/pooling.py +151 -0
- mssql_python/py.typed +1 -0
- mssql_python/row.py +398 -0
- mssql_python/type.py +160 -0
- mssql_python-1.16.0.dist-info/METADATA +102 -0
- mssql_python-1.16.0.dist-info/RECORD +36 -0
- mssql_python-1.16.0.dist-info/WHEEL +5 -0
- mssql_python-1.16.0.dist-info/licenses/LICENSE +47 -0
- mssql_python-1.16.0.dist-info/top_level.txt +1 -0
mssql_python/row.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
Licensed under the MIT license.
|
|
4
|
+
This module contains the Row class, which represents a single row of data
|
|
5
|
+
from a cursor fetch operation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import decimal
|
|
9
|
+
import uuid as _uuid
|
|
10
|
+
from collections.abc import Mapping
|
|
11
|
+
from typing import Any
|
|
12
|
+
from mssql_python.logging import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Row:
|
|
16
|
+
"""
|
|
17
|
+
A row of data from a cursor fetch operation. Provides both tuple-like indexing
|
|
18
|
+
and attribute access to column values.
|
|
19
|
+
|
|
20
|
+
Rows support user-defined attributes, ``vars(row)``, and weak references.
|
|
21
|
+
Internal fields use slots; ``vars(row)`` contains user-defined attributes.
|
|
22
|
+
|
|
23
|
+
For dict-like access, use the read-only ``row._mapping`` view (a
|
|
24
|
+
``collections.abc.Mapping`` of column name -> value). Iterating the Row itself
|
|
25
|
+
(for x in row) yields values, not keys — consistent with pyodbc.Row and
|
|
26
|
+
sqlite3.Row; iterate ``row._mapping`` to get column names.
|
|
27
|
+
|
|
28
|
+
Column attribute access behavior depends on the global 'lowercase' setting:
|
|
29
|
+
- When enabled: Case-insensitive attribute access
|
|
30
|
+
- When disabled (default): Case-sensitive attribute access matching original column names
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
row = cursor.fetchone()
|
|
34
|
+
print(row[0]) # Access by index
|
|
35
|
+
print(row.column_name) # Access by column name
|
|
36
|
+
print(dict(row._mapping)) # Convert to a plain dict
|
|
37
|
+
print(row._mapping["col"]) # Access a value by column name via the mapping
|
|
38
|
+
for name in row._mapping: # Iterate column names
|
|
39
|
+
print(name, row._mapping[name])
|
|
40
|
+
for value in row: # Iterating the Row yields values, not keys
|
|
41
|
+
print(value)
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Slot internal fields while preserving dynamic attributes and weak references.
|
|
45
|
+
__slots__ = (
|
|
46
|
+
"_values",
|
|
47
|
+
"_column_map",
|
|
48
|
+
"_cursor",
|
|
49
|
+
"_column_map_lower",
|
|
50
|
+
"_column_names",
|
|
51
|
+
"__dict__",
|
|
52
|
+
"__weakref__",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _fast_create(values, column_map, cursor, column_map_lower=None, column_names=None):
|
|
57
|
+
"""Construct a Row bypassing __init__ — for the common fast path.
|
|
58
|
+
|
|
59
|
+
Used by fetchall/fetchmany when no output converters and no UUID
|
|
60
|
+
stringification are needed (the vast majority of queries). Skips
|
|
61
|
+
the entire if/elif/else chain and keyword argument overhead in __init__.
|
|
62
|
+
"""
|
|
63
|
+
r = Row.__new__(Row)
|
|
64
|
+
r._values = values
|
|
65
|
+
r._column_map = column_map
|
|
66
|
+
r._cursor = cursor
|
|
67
|
+
r._column_map_lower = column_map_lower
|
|
68
|
+
r._column_names = column_names
|
|
69
|
+
return r
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
values,
|
|
74
|
+
column_map,
|
|
75
|
+
cursor=None,
|
|
76
|
+
converter_map=None,
|
|
77
|
+
uuid_str_indices=None,
|
|
78
|
+
column_map_lower=None,
|
|
79
|
+
column_names=None,
|
|
80
|
+
):
|
|
81
|
+
"""
|
|
82
|
+
Initialize a Row object with values and pre-built column map.
|
|
83
|
+
Args:
|
|
84
|
+
values: List of values for this row
|
|
85
|
+
column_map: Pre-built column name to index mapping (shared across rows)
|
|
86
|
+
cursor: Optional cursor reference (for backward compatibility and lowercase access)
|
|
87
|
+
converter_map: Pre-computed converter map (shared across rows for performance).
|
|
88
|
+
An empty sequence skips converters; None enables the connection fallback.
|
|
89
|
+
uuid_str_indices: Tuple of column indices whose uuid.UUID values should be
|
|
90
|
+
converted to str. Pre-computed once per result set when native_uuid=False.
|
|
91
|
+
None means no conversion (native_uuid=True, the default).
|
|
92
|
+
column_map_lower: Pre-built lowercase column map for O(1) case-insensitive
|
|
93
|
+
lookups. Built once per result set in the cursor when lowercase is enabled;
|
|
94
|
+
None when lowercase is off (the default). Shared across all rows.
|
|
95
|
+
column_names: Canonical, order- and duplicate-preserving column names for
|
|
96
|
+
the result set, snapshotted once by the cursor and shared by reference
|
|
97
|
+
across all rows. Backs ``row._mapping``. None for rows built without a
|
|
98
|
+
cursor snapshot; ``_mapping_keys()`` then reconstructs names from
|
|
99
|
+
``column_map``.
|
|
100
|
+
"""
|
|
101
|
+
if converter_map:
|
|
102
|
+
self._values = self._apply_output_converters_optimized(values, converter_map)
|
|
103
|
+
elif (
|
|
104
|
+
converter_map is None
|
|
105
|
+
and cursor
|
|
106
|
+
and hasattr(cursor.connection, "_output_converters")
|
|
107
|
+
and cursor.connection._output_converters
|
|
108
|
+
):
|
|
109
|
+
# Support direct Row construction without a pre-computed converter map.
|
|
110
|
+
self._values = self._apply_output_converters(values, cursor)
|
|
111
|
+
else:
|
|
112
|
+
self._values = values
|
|
113
|
+
|
|
114
|
+
if uuid_str_indices:
|
|
115
|
+
self._stringify_uuids(uuid_str_indices)
|
|
116
|
+
|
|
117
|
+
self._column_map = column_map
|
|
118
|
+
self._cursor = cursor
|
|
119
|
+
# Lowercase map is pre-built once per result set in the cursor and shared
|
|
120
|
+
# across all rows. None when lowercase is off (the default) — zero cost.
|
|
121
|
+
self._column_map_lower = column_map_lower
|
|
122
|
+
# Canonical column names for this row's result set, snapshotted once by the
|
|
123
|
+
# cursor (order- and duplicate-preserving) and shared by reference across every
|
|
124
|
+
# row. None only for rows built without a cursor snapshot (e.g. some direct or
|
|
125
|
+
# test constructions); _mapping_keys() then reconstructs names from _column_map.
|
|
126
|
+
self._column_names = column_names
|
|
127
|
+
|
|
128
|
+
def _stringify_uuids(self, indices):
|
|
129
|
+
"""
|
|
130
|
+
Convert uuid.UUID values at the given column indices to uppercase str in-place.
|
|
131
|
+
|
|
132
|
+
This is only called when native_uuid=False. It operates directly on
|
|
133
|
+
self._values to avoid creating an extra list copy.
|
|
134
|
+
"""
|
|
135
|
+
vals = self._values
|
|
136
|
+
# If values are still the original list (no converters), we need a mutable copy
|
|
137
|
+
if not isinstance(vals, list):
|
|
138
|
+
vals = list(vals)
|
|
139
|
+
self._values = vals
|
|
140
|
+
|
|
141
|
+
for i in indices:
|
|
142
|
+
v = vals[i]
|
|
143
|
+
if v is not None and isinstance(v, _uuid.UUID):
|
|
144
|
+
vals[i] = str(v).upper()
|
|
145
|
+
|
|
146
|
+
def _apply_output_converters(self, values, cursor):
|
|
147
|
+
"""
|
|
148
|
+
Apply output converters to raw values.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
values: Raw values from the database
|
|
152
|
+
cursor: Cursor object with connection and description
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
List of converted values
|
|
156
|
+
"""
|
|
157
|
+
if not cursor.description:
|
|
158
|
+
return values
|
|
159
|
+
|
|
160
|
+
converted_values = list(values)
|
|
161
|
+
|
|
162
|
+
for i, (value, desc) in enumerate(zip(values, cursor.description)):
|
|
163
|
+
if desc is None or value is None:
|
|
164
|
+
continue
|
|
165
|
+
|
|
166
|
+
# Get SQL type from description
|
|
167
|
+
sql_type = desc[1] # type_code is at index 1 in description tuple
|
|
168
|
+
|
|
169
|
+
# Try to get a converter for this type
|
|
170
|
+
converter = cursor.connection.get_output_converter(sql_type)
|
|
171
|
+
|
|
172
|
+
# If no converter found for the SQL type but the value is a string or bytes,
|
|
173
|
+
# try the WVARCHAR converter as a fallback
|
|
174
|
+
if converter is None and isinstance(value, (str, bytes)):
|
|
175
|
+
from mssql_python.constants import ConstantsDDBC
|
|
176
|
+
|
|
177
|
+
converter = cursor.connection.get_output_converter(ConstantsDDBC.SQL_WVARCHAR.value)
|
|
178
|
+
|
|
179
|
+
# If we found a converter, apply it
|
|
180
|
+
if converter:
|
|
181
|
+
try:
|
|
182
|
+
# If value is already a Python type (str, int, etc.),
|
|
183
|
+
# we need to convert it to bytes for our converters
|
|
184
|
+
if isinstance(value, str):
|
|
185
|
+
# Encode as UTF-16LE for string values (SQL_WVARCHAR format)
|
|
186
|
+
value_bytes = value.encode("utf-16-le")
|
|
187
|
+
converted_values[i] = converter(value_bytes)
|
|
188
|
+
else:
|
|
189
|
+
converted_values[i] = converter(value)
|
|
190
|
+
except Exception:
|
|
191
|
+
logger.debug("Exception occurred in output converter", exc_info=True)
|
|
192
|
+
# If conversion fails, keep the original value
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
return converted_values
|
|
196
|
+
|
|
197
|
+
def _apply_output_converters_optimized(self, values, converter_map):
|
|
198
|
+
"""
|
|
199
|
+
Apply output converters using pre-computed converter map for optimal performance.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
values: Raw values from the database
|
|
203
|
+
converter_map: Pre-computed list of converters (one per column, None if no converter)
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of converted values
|
|
207
|
+
"""
|
|
208
|
+
converted_values = list(values)
|
|
209
|
+
|
|
210
|
+
for i, (value, converter) in enumerate(zip(values, converter_map)):
|
|
211
|
+
if converter and value is not None:
|
|
212
|
+
try:
|
|
213
|
+
if isinstance(value, str):
|
|
214
|
+
value_bytes = value.encode("utf-16-le")
|
|
215
|
+
converted_values[i] = converter(value_bytes)
|
|
216
|
+
else:
|
|
217
|
+
converted_values[i] = converter(value)
|
|
218
|
+
except Exception:
|
|
219
|
+
pass
|
|
220
|
+
|
|
221
|
+
return converted_values
|
|
222
|
+
|
|
223
|
+
def __getitem__(self, index) -> Any:
|
|
224
|
+
"""Allow accessing by numeric index (row[0]) or column name (row["col"])."""
|
|
225
|
+
if type(index) is int:
|
|
226
|
+
return self._values[index]
|
|
227
|
+
if isinstance(index, str):
|
|
228
|
+
# A row built without a column map has no named columns, so any
|
|
229
|
+
# string key is simply absent (KeyError), never a TypeError.
|
|
230
|
+
if self._column_map is not None and index in self._column_map:
|
|
231
|
+
return self._values[self._column_map[index]]
|
|
232
|
+
# O(1) case-insensitive lookup when lowercase is enabled
|
|
233
|
+
if self._column_map_lower is not None:
|
|
234
|
+
idx = self._column_map_lower.get(index.lower())
|
|
235
|
+
if idx is not None:
|
|
236
|
+
return self._values[idx]
|
|
237
|
+
raise KeyError(f"Row has no column '{index}'")
|
|
238
|
+
if isinstance(index, (int, slice)):
|
|
239
|
+
return self._values[index]
|
|
240
|
+
raise TypeError(
|
|
241
|
+
f"Row indices must be integers, slices, or strings, not {type(index).__name__}"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def __getattr__(self, name: str) -> Any:
|
|
245
|
+
"""
|
|
246
|
+
Allow accessing by column name as attribute: row.column_name
|
|
247
|
+
|
|
248
|
+
Note: Case sensitivity depends on the global 'lowercase' setting:
|
|
249
|
+
- When lowercase=True: Column names are stored in lowercase, enabling
|
|
250
|
+
case-insensitive attribute access (e.g., row.NAME, row.name, row.Name all work).
|
|
251
|
+
- When lowercase=False (default): Column names preserve original casing,
|
|
252
|
+
requiring exact case matching for attribute access.
|
|
253
|
+
"""
|
|
254
|
+
# Handle lowercase attribute access - if lowercase is enabled,
|
|
255
|
+
# try to match attribute names case-insensitively
|
|
256
|
+
if name in self._column_map:
|
|
257
|
+
return self._values[self._column_map[name]]
|
|
258
|
+
|
|
259
|
+
# O(1) case-insensitive lookup when lowercase is enabled
|
|
260
|
+
if self._column_map_lower is not None:
|
|
261
|
+
idx = self._column_map_lower.get(name.lower())
|
|
262
|
+
if idx is not None:
|
|
263
|
+
return self._values[idx]
|
|
264
|
+
|
|
265
|
+
raise AttributeError(f"Row has no attribute '{name}'")
|
|
266
|
+
|
|
267
|
+
@property
|
|
268
|
+
def _mapping(self) -> "RowMapping":
|
|
269
|
+
"""Read-only ``dict``-like view (column name -> value) over this row.
|
|
270
|
+
|
|
271
|
+
Returns a :class:`RowMapping` (a ``collections.abc.Mapping``). Typical use::
|
|
272
|
+
|
|
273
|
+
row = cursor.fetchone()
|
|
274
|
+
dict(row._mapping) # {'id': 1, 'name': 'Alice'}
|
|
275
|
+
for name, value in row._mapping.items():
|
|
276
|
+
...
|
|
277
|
+
row._mapping["name"] # value by column name
|
|
278
|
+
"name" in row._mapping # membership by column name
|
|
279
|
+
|
|
280
|
+
Semantics and caveats:
|
|
281
|
+
|
|
282
|
+
- Keys are the result set's column names, order-preserving and
|
|
283
|
+
de-duplicated: when a name repeats, one key is kept and the last column
|
|
284
|
+
with that name supplies its value (matching ``row[name]`` / ``row.name``).
|
|
285
|
+
Every duplicate value stays reachable positionally via ``row[i]``.
|
|
286
|
+
- Lookup and membership use the canonical column names exactly, so the key
|
|
287
|
+
set, ``in`` and ``[]`` always agree. Unlike ``row[name]`` / ``row.name``,
|
|
288
|
+
the view does NOT resolve case-insensitive names or catalog aliases; with
|
|
289
|
+
``lowercase=True`` the keys are the lowercased names.
|
|
290
|
+
- ``_mapping`` is a property, so a column literally named ``_mapping`` is
|
|
291
|
+
shadowed: read it with ``row["_mapping"]`` or ``row._mapping["_mapping"]``.
|
|
292
|
+
"""
|
|
293
|
+
return RowMapping(self)
|
|
294
|
+
|
|
295
|
+
def _mapping_keys(self) -> tuple:
|
|
296
|
+
"""Canonical, order-preserving column names backing ``_mapping``.
|
|
297
|
+
|
|
298
|
+
Prefers the names snapshotted once by the cursor for the result set, which
|
|
299
|
+
preserve the result set's column order. When a row was built without that
|
|
300
|
+
snapshot (e.g. a direct ``Row(values, column_map)`` construction), names are
|
|
301
|
+
reconstructed from ``_column_map`` in column-index order; that order can
|
|
302
|
+
differ from ``_column_map``'s insertion order, so a directly-constructed row
|
|
303
|
+
may key differently from an otherwise-equivalent cursor row. Returns ``()``
|
|
304
|
+
when neither source is available. Normal cursor fetches always supply the
|
|
305
|
+
snapshot.
|
|
306
|
+
"""
|
|
307
|
+
if self._column_names is not None:
|
|
308
|
+
return self._column_names
|
|
309
|
+
if self._column_map:
|
|
310
|
+
idx_to_name: dict = {}
|
|
311
|
+
for name, idx in self._column_map.items():
|
|
312
|
+
idx_to_name.setdefault(idx, name)
|
|
313
|
+
return tuple(idx_to_name[i] for i in sorted(idx_to_name))
|
|
314
|
+
return ()
|
|
315
|
+
|
|
316
|
+
def __eq__(self, other: Any) -> bool:
|
|
317
|
+
"""
|
|
318
|
+
Support comparison with lists for test compatibility.
|
|
319
|
+
This is the key change needed to fix the tests.
|
|
320
|
+
"""
|
|
321
|
+
if isinstance(other, list):
|
|
322
|
+
return self._values == other
|
|
323
|
+
if isinstance(other, Row):
|
|
324
|
+
return self._values == other._values
|
|
325
|
+
return super().__eq__(other)
|
|
326
|
+
|
|
327
|
+
def __len__(self) -> int:
|
|
328
|
+
"""Return the number of values in the row"""
|
|
329
|
+
return len(self._values)
|
|
330
|
+
|
|
331
|
+
def __iter__(self) -> Any:
|
|
332
|
+
"""Allow iteration through values"""
|
|
333
|
+
return iter(self._values)
|
|
334
|
+
|
|
335
|
+
def __str__(self) -> str:
|
|
336
|
+
"""Return string representation of the row"""
|
|
337
|
+
# Local import to avoid circular dependency
|
|
338
|
+
from mssql_python import getDecimalSeparator
|
|
339
|
+
|
|
340
|
+
parts = []
|
|
341
|
+
for value in self:
|
|
342
|
+
if isinstance(value, decimal.Decimal):
|
|
343
|
+
# Apply custom decimal separator for display
|
|
344
|
+
sep = getDecimalSeparator()
|
|
345
|
+
if sep != "." and value is not None:
|
|
346
|
+
s = str(value)
|
|
347
|
+
if "." in s:
|
|
348
|
+
s = s.replace(".", sep)
|
|
349
|
+
parts.append(s)
|
|
350
|
+
else:
|
|
351
|
+
parts.append(str(value))
|
|
352
|
+
else:
|
|
353
|
+
parts.append(repr(value))
|
|
354
|
+
|
|
355
|
+
return "(" + ", ".join(parts) + ")"
|
|
356
|
+
|
|
357
|
+
def __repr__(self) -> str:
|
|
358
|
+
"""Return a detailed string representation for debugging"""
|
|
359
|
+
return repr(tuple(self._values))
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class RowMapping(Mapping):
|
|
363
|
+
"""Read-only ``Mapping`` view over a :class:`Row` (column name -> value).
|
|
364
|
+
|
|
365
|
+
Created via :attr:`Row._mapping`. Keys are the row's canonical column names,
|
|
366
|
+
order-preserving and de-duplicated (last column wins for a repeated name).
|
|
367
|
+
Lookup and membership use those names exactly -- no case-insensitive or catalog
|
|
368
|
+
alias resolution -- so iteration, ``in`` and ``[]`` always agree. The view
|
|
369
|
+
reflects the row it wraps and copies no values.
|
|
370
|
+
"""
|
|
371
|
+
|
|
372
|
+
__slots__ = ("_row",)
|
|
373
|
+
|
|
374
|
+
def __init__(self, row: "Row") -> None:
|
|
375
|
+
self._row = row
|
|
376
|
+
|
|
377
|
+
def __getitem__(self, key: str) -> Any:
|
|
378
|
+
# Restrict lookups to the canonical column names yielded by __iter__ so
|
|
379
|
+
# membership and lookup agree with iteration (proper Mapping semantics).
|
|
380
|
+
# Row.__getitem__ additionally accepts case-insensitive names and catalog
|
|
381
|
+
# aliases, but those are not iterated keys, so the view must not resolve
|
|
382
|
+
# them here.
|
|
383
|
+
if isinstance(key, str) and key in self._row._mapping_keys():
|
|
384
|
+
return self._row[key]
|
|
385
|
+
raise KeyError(key)
|
|
386
|
+
|
|
387
|
+
def __iter__(self):
|
|
388
|
+
seen = set()
|
|
389
|
+
for name in self._row._mapping_keys():
|
|
390
|
+
if name not in seen:
|
|
391
|
+
seen.add(name)
|
|
392
|
+
yield name
|
|
393
|
+
|
|
394
|
+
def __len__(self) -> int:
|
|
395
|
+
return sum(1 for _ in self)
|
|
396
|
+
|
|
397
|
+
def __repr__(self) -> str:
|
|
398
|
+
return f"RowMapping({dict(self)!r})"
|
mssql_python/type.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
Licensed under the MIT license.
|
|
4
|
+
This module contains type objects and constructors for the mssql_python package.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
import time
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Type Objects
|
|
13
|
+
class STRING(str):
|
|
14
|
+
"""
|
|
15
|
+
This type object is used to describe columns in a database that are string-based (e.g. CHAR).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __new__(cls):
|
|
19
|
+
return str.__new__(cls, "")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class BINARY(bytearray):
|
|
23
|
+
"""
|
|
24
|
+
This type object is used to describe (long)
|
|
25
|
+
binary columns in a database (e.g. LONG, RAW, BLOBs).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __new__(cls):
|
|
29
|
+
return bytearray.__new__(cls)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class NUMBER(float):
|
|
33
|
+
"""
|
|
34
|
+
This type object is used to describe numeric columns in a database.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __new__(cls):
|
|
38
|
+
return float.__new__(cls, 0.0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class DATETIME(datetime.datetime):
|
|
42
|
+
"""
|
|
43
|
+
This type object is used to describe date/time columns in a database.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def __new__(
|
|
47
|
+
cls,
|
|
48
|
+
year: int = 1,
|
|
49
|
+
month: int = 1,
|
|
50
|
+
day: int = 1,
|
|
51
|
+
hour: int = 0,
|
|
52
|
+
minute: int = 0,
|
|
53
|
+
second: int = 0,
|
|
54
|
+
microsecond: int = 0,
|
|
55
|
+
tzinfo=None,
|
|
56
|
+
*,
|
|
57
|
+
fold: int = 0,
|
|
58
|
+
):
|
|
59
|
+
return datetime.datetime.__new__(
|
|
60
|
+
cls, year, month, day, hour, minute, second, microsecond, tzinfo, fold=fold
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ROWID(int):
|
|
65
|
+
"""
|
|
66
|
+
This type object is used to describe the "Row ID" column in a database.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __new__(cls):
|
|
70
|
+
return int.__new__(cls, 0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Type Constructors
|
|
74
|
+
def Date(year: int, month: int, day: int) -> datetime.date:
|
|
75
|
+
"""
|
|
76
|
+
Generates a date object.
|
|
77
|
+
"""
|
|
78
|
+
return datetime.date(year, month, day)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def Time(hour: int, minute: int, second: int) -> datetime.time:
|
|
82
|
+
"""
|
|
83
|
+
Generates a time object.
|
|
84
|
+
"""
|
|
85
|
+
return datetime.time(hour, minute, second)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def Timestamp(
|
|
89
|
+
year: int,
|
|
90
|
+
month: int,
|
|
91
|
+
day: int,
|
|
92
|
+
hour: int,
|
|
93
|
+
minute: int,
|
|
94
|
+
second: int,
|
|
95
|
+
microsecond: int,
|
|
96
|
+
) -> datetime.datetime:
|
|
97
|
+
"""
|
|
98
|
+
Generates a timestamp object.
|
|
99
|
+
"""
|
|
100
|
+
return datetime.datetime(year, month, day, hour, minute, second, microsecond)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def DateFromTicks(ticks: int) -> datetime.date:
|
|
104
|
+
"""
|
|
105
|
+
Generates a date object from ticks.
|
|
106
|
+
"""
|
|
107
|
+
return datetime.date.fromtimestamp(ticks)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def TimeFromTicks(ticks: int) -> datetime.time:
|
|
111
|
+
"""
|
|
112
|
+
Generates a time object from ticks.
|
|
113
|
+
"""
|
|
114
|
+
return datetime.time(*time.localtime(ticks)[3:6])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def TimestampFromTicks(ticks: int) -> datetime.datetime:
|
|
118
|
+
"""
|
|
119
|
+
Generates a timestamp object from ticks.
|
|
120
|
+
"""
|
|
121
|
+
return datetime.datetime.fromtimestamp(ticks)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def Binary(value: Union[str, bytes, bytearray, memoryview]) -> bytes:
|
|
125
|
+
"""
|
|
126
|
+
Converts a string or bytes-like object to bytes for binary database columns.
|
|
127
|
+
|
|
128
|
+
This function follows the DB-API 2.0 specification. It accepts str,
|
|
129
|
+
bytes, bytearray, and memoryview. memoryview is accepted so buffer-protocol
|
|
130
|
+
values (for example the memoryview Django hands a BinaryField) round-trip the
|
|
131
|
+
same way pyodbc handles them.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
value: A string (str) or bytes-like object (bytes, bytearray, memoryview)
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
bytes: The input converted to bytes
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
TypeError: If the input type is not supported
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
Binary("hello") # Returns b"hello"
|
|
144
|
+
Binary(b"hello") # Returns b"hello"
|
|
145
|
+
Binary(bytearray(b"hi")) # Returns b"hi"
|
|
146
|
+
Binary(memoryview(b"hi")) # Returns b"hi"
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(value, bytes):
|
|
149
|
+
return value
|
|
150
|
+
if isinstance(value, bytearray):
|
|
151
|
+
return bytes(value)
|
|
152
|
+
if isinstance(value, memoryview):
|
|
153
|
+
return value.tobytes()
|
|
154
|
+
if isinstance(value, str):
|
|
155
|
+
return value.encode("utf-8")
|
|
156
|
+
# Raise TypeError for unsupported types to improve type safety
|
|
157
|
+
raise TypeError(
|
|
158
|
+
f"Cannot convert type {type(value).__name__} to bytes. "
|
|
159
|
+
f"Binary() only accepts str, bytes, bytearray, or memoryview objects."
|
|
160
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mssql-python
|
|
3
|
+
Version: 1.16.0
|
|
4
|
+
Summary: A Python library for interacting with Microsoft SQL Server
|
|
5
|
+
Home-page: https://github.com/microsoft/mssql-python
|
|
6
|
+
Author: Microsoft Corporation
|
|
7
|
+
Author-email: mssql-python@microsoft.com
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: azure-identity>=1.12.0
|
|
15
|
+
Requires-Dist: mssql-python-odbc==18.6.2.2
|
|
16
|
+
Requires-Dist: mssql-python-rs==0.3.0
|
|
17
|
+
Provides-Extra: pyarrow
|
|
18
|
+
Requires-Dist: pyarrow>=14.0.0; extra == "pyarrow"
|
|
19
|
+
Dynamic: author
|
|
20
|
+
Dynamic: author-email
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
Dynamic: provides-extra
|
|
27
|
+
Dynamic: requires-dist
|
|
28
|
+
Dynamic: requires-python
|
|
29
|
+
Dynamic: summary
|
|
30
|
+
|
|
31
|
+
# General Availability Release
|
|
32
|
+
|
|
33
|
+
mssql‑python is now Generally Available (GA) as Microsoft’s official Python driver for SQL Server, Azure SQL, and SQL databases in Fabric. This release delivers a production‑ready, high‑performance, and developer‑friendly experience.
|
|
34
|
+
|
|
35
|
+
## What makes mssql-python different?
|
|
36
|
+
|
|
37
|
+
### Powered by DDBC – Direct Database Connectivity
|
|
38
|
+
|
|
39
|
+
Most Python SQL Server drivers, including pyodbc, route calls through the Driver Manager, which has slightly different implementations across Windows, macOS, and Linux. This results in inconsistent behavior and capabilities across platforms. Additionally, the Driver Manager must be installed separately, creating friction for both new developers and when deploying applications to servers.
|
|
40
|
+
|
|
41
|
+
At the heart of the mssql-python driver is DDBC (Direct Database Connectivity) — a lightweight, high-performance C++ layer that replaces the platform’s Driver Manager.
|
|
42
|
+
|
|
43
|
+
Key Advantages:
|
|
44
|
+
|
|
45
|
+
- Provides a consistent, cross-platform backend that handles connections, statements, and memory directly.
|
|
46
|
+
- Interfaces directly with the native SQL Server drivers.
|
|
47
|
+
- Integrates with the same TDS core library that powers the ODBC driver.
|
|
48
|
+
|
|
49
|
+
### Why is this architecture important?
|
|
50
|
+
|
|
51
|
+
By simplifying the architecture, DDBC delivers:
|
|
52
|
+
|
|
53
|
+
- Consistency across platforms
|
|
54
|
+
- Lower function call overhead
|
|
55
|
+
- Zero external dependencies on Windows (`pip install mssql-python` is all you need)
|
|
56
|
+
- Full control over connections, memory, and statement handling
|
|
57
|
+
|
|
58
|
+
### Built with PyBind11 + Modern C++ for Performance and Safety
|
|
59
|
+
|
|
60
|
+
To expose the DDBC engine to Python, mssql-python uses PyBind11 – a modern C++ binding library.
|
|
61
|
+
|
|
62
|
+
PyBind11 provides:
|
|
63
|
+
|
|
64
|
+
- Native-speed execution with automatic type conversions
|
|
65
|
+
- Memory-safe bindings
|
|
66
|
+
- Clean and Pythonic API, while performance-critical logic remains in robust, maintainable C++.
|
|
67
|
+
|
|
68
|
+
## What's new in v1.16.0
|
|
69
|
+
|
|
70
|
+
### Enhancements
|
|
71
|
+
|
|
72
|
+
- **Python 3.15 Support** - Release wheels and validation now cover Python 3.15 across supported platforms (#817).
|
|
73
|
+
- **Dictionary-Style Row Access** - `row._mapping` provides a read-only mapping view with dictionary conversion, keys, values, items, membership checks, and stable column metadata (#613).
|
|
74
|
+
- **External Rust Runtime Dependency** - `mssql-python` now depends on the exact compatible `mssql-python-rs` package instead of embedding its files (#768).
|
|
75
|
+
- **Optional `mssql-odbc` Provider** - Customers can opt into the Rust-based provider distributed with `mssql-python-rs` by setting `MSSQL_PYTHON_NATIVE_PROVIDER=mssql-odbc` or `mssql_python.native_provider = "mssql-odbc"` before the first connection; Microsoft ODBC Driver 18 (`msodbcsql18`) remains the default (#730, #804, via `mssql-python-rs`).
|
|
76
|
+
- **Stable-ABI Rust Runtime Wheels** - The Rust runtime now uses CPython's stable ABI and `mssql-python-rs` 0.3.0, eliminating Python-version-specific wheel variants while retaining separate wheels for each supported OS and architecture (#804, via `mssql-python-rs`).
|
|
77
|
+
- **Updated ODBC Runtime Package** - `mssql-python` now pins `mssql-python-odbc==18.6.2.2`, whose Linux wheels separate glibc and musl payloads while retaining the same ODBC Driver 18.6 binaries (#798, #807).
|
|
78
|
+
- **Faster Row Construction and Batch Fetching** - Optimized native Row construction and cursor bookkeeping reduce Python-side overhead in `fetchone()`, `fetchmany()`, and `fetchall()` (#558).
|
|
79
|
+
- **Faster Temporal Fetch Conversion** - DATE, TIME, and TIMESTAMP values use checked CPython constructors to reduce object-construction overhead (#795).
|
|
80
|
+
- **Reusable Result-Set Metadata** - Stable native column metadata is reused within a result set instead of being repeatedly described (#796).
|
|
81
|
+
- **Reduced Fetch Diagnostic Overhead** - Fetch diagnostics are captured once and reused instead of being redundantly rescanned after clean fetches (#809).
|
|
82
|
+
- **Faster Integer Row Indexing** - Exact integer indexes use a direct fast path while preserving existing behavior for booleans, subclasses, slices, and column names (#822).
|
|
83
|
+
- **Lower `fetchval()` Logging Overhead** - Disabled debug logging no longer dispatches unnecessary log calls from `fetchval()` (#823).
|
|
84
|
+
|
|
85
|
+
### Bug Fixes
|
|
86
|
+
|
|
87
|
+
- **Reliable Windows Shutdown** - Late ODBC environment cleanup no longer causes process-exit access violations after Python finalization (#783).
|
|
88
|
+
- **Preserved Leading BOM-Like Characters** - Bounded UTF-16 text keeps leading U+FEFF and U+FFFE code points as data across fetch paths (#793).
|
|
89
|
+
- **Clean Pooled Connection State** - Connections are rolled back and restored to autocommit before returning to the pool, preventing retained empty transactions (#777).
|
|
90
|
+
- **Safe Native Logging of Dynamic Metadata** - Server-controlled Arrow column metadata can no longer be interpreted as a native format string (#791).
|
|
91
|
+
- **UDT Parameter Binding** - SQL Server UDT parameter identities are resolved before scalar or array binding, including typed NULLs and statement reuse (#818).
|
|
92
|
+
- **Visible Unsupported Encoding Requests** - `setencoding()` now warns when a valid encoding request cannot be applied instead of silently ignoring it (#828).
|
|
93
|
+
- **Bounded Bulk Copy Timeouts** - Positive sub-second bulk-copy timeouts no longer become infinite and are rounded to one second (#804, via `mssql-python-rs`).
|
|
94
|
+
- **Safe Bulk Copy Identifiers** - Bulk-copy destinations, columns, and collations are parsed, quoted, and validated consistently (#804, via `mssql-python-rs`).
|
|
95
|
+
|
|
96
|
+
For more information, please visit the project link on Github: https://github.com/microsoft/mssql-python
|
|
97
|
+
|
|
98
|
+
If you have any feedback, questions or need support please mail us at mssql-python@microsoft.com.
|
|
99
|
+
|
|
100
|
+
## What's Next
|
|
101
|
+
|
|
102
|
+
As we continue to refine the driver and add new features, you can expect regular updates, optimizations, and bug fixes. We encourage you to contribute, provide feedback and report any issues you encounter, as this will help us improve the driver.
|