mssql-python 1.16.0__cp315-cp315-win_amd64.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-amd64.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
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Asynchronous result fetching through mssql-py-core."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from ..exceptions import OperationalError, ProgrammingError
|
|
8
|
+
from ..logging import logger
|
|
9
|
+
from ..row import Row
|
|
10
|
+
from .exception_translator import translate_py_core_exceptions
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .async_cursor import _AsyncCursor # pyright: ignore[reportPrivateUsage]
|
|
14
|
+
|
|
15
|
+
_ResultSnapshot = tuple[
|
|
16
|
+
int,
|
|
17
|
+
dict[str, int],
|
|
18
|
+
tuple[int, ...] | None,
|
|
19
|
+
dict[str, int] | None,
|
|
20
|
+
tuple[str, ...] | None,
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _get_py_core_async_cursor(cursor: "_AsyncCursor") -> Any:
|
|
25
|
+
return cursor._py_core_async_cursor # pyright: ignore[reportPrivateUsage]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _snapshot_result(cursor: "_AsyncCursor") -> _ResultSnapshot:
|
|
29
|
+
return (
|
|
30
|
+
cursor._result_generation, # pyright: ignore[reportPrivateUsage]
|
|
31
|
+
cursor._column_map, # pyright: ignore[reportPrivateUsage]
|
|
32
|
+
cursor._uuid_str_indices, # pyright: ignore[reportPrivateUsage]
|
|
33
|
+
cursor._column_map_lower, # pyright: ignore[reportPrivateUsage]
|
|
34
|
+
cursor._column_names, # pyright: ignore[reportPrivateUsage]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _reconcile_failed_fetch(
|
|
39
|
+
cursor: "_AsyncCursor", generation: int, operation: str, error: BaseException
|
|
40
|
+
) -> None:
|
|
41
|
+
if generation != cursor._result_generation: # pyright: ignore[reportPrivateUsage]
|
|
42
|
+
return
|
|
43
|
+
if isinstance(error, OperationalError) and str(error.__cause__).startswith(
|
|
44
|
+
"Connection is busy"
|
|
45
|
+
):
|
|
46
|
+
return
|
|
47
|
+
logger.debug("AsyncCursor.%s: invalidating result state after fetch failure", operation)
|
|
48
|
+
cursor._reset_fetch_tracking() # pyright: ignore[reportPrivateUsage]
|
|
49
|
+
cursor._clear_result_metadata() # pyright: ignore[reportPrivateUsage]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _wrap_row(snapshot: _ResultSnapshot, values: tuple[Any, ...]) -> Row:
|
|
53
|
+
_, column_map, uuid_str_indices, column_map_lower, column_names = snapshot
|
|
54
|
+
return Row(
|
|
55
|
+
list(values),
|
|
56
|
+
column_map,
|
|
57
|
+
uuid_str_indices=uuid_str_indices,
|
|
58
|
+
column_map_lower=column_map_lower,
|
|
59
|
+
column_names=column_names,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def fetchone(cursor: "_AsyncCursor") -> Row | None:
|
|
64
|
+
"""Fetch the next row through the py-core async cursor."""
|
|
65
|
+
logger.debug("AsyncCursor.fetchone: starting")
|
|
66
|
+
await cursor._wait_for_result_publication() # pyright: ignore[reportPrivateUsage]
|
|
67
|
+
snapshot = _snapshot_result(cursor)
|
|
68
|
+
try:
|
|
69
|
+
with translate_py_core_exceptions():
|
|
70
|
+
row = await _get_py_core_async_cursor(cursor).fetchone()
|
|
71
|
+
except (Exception, asyncio.CancelledError) as error:
|
|
72
|
+
_reconcile_failed_fetch(cursor, snapshot[0], "fetchone", error)
|
|
73
|
+
raise
|
|
74
|
+
cursor._record_fetch( # pyright: ignore[reportPrivateUsage]
|
|
75
|
+
snapshot[0], row is not None, row is None
|
|
76
|
+
)
|
|
77
|
+
logger.debug(
|
|
78
|
+
"AsyncCursor.fetchone: completed; row_found=%s; rowcount=%d",
|
|
79
|
+
row is not None,
|
|
80
|
+
cursor.rowcount,
|
|
81
|
+
)
|
|
82
|
+
return None if row is None else _wrap_row(snapshot, row)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def fetchmany(cursor: "_AsyncCursor", size: int | None = None) -> list[Row]:
|
|
86
|
+
"""Fetch up to size rows, using cursor arraysize when size is omitted."""
|
|
87
|
+
await cursor._wait_for_result_publication() # pyright: ignore[reportPrivateUsage]
|
|
88
|
+
cursor._check_closed() # pyright: ignore[reportPrivateUsage]
|
|
89
|
+
requested_size = cursor.arraysize if size is None else size
|
|
90
|
+
logger.debug("AsyncCursor.fetchmany: starting; requested_size=%s", requested_size)
|
|
91
|
+
if requested_size <= 0:
|
|
92
|
+
if cursor.description is None:
|
|
93
|
+
raise ProgrammingError("Async operation failed", "No active result set")
|
|
94
|
+
logger.debug("AsyncCursor.fetchmany: completed; row_count=0; rowcount=%d", cursor.rowcount)
|
|
95
|
+
return []
|
|
96
|
+
snapshot = _snapshot_result(cursor)
|
|
97
|
+
with translate_py_core_exceptions():
|
|
98
|
+
if size is None:
|
|
99
|
+
fetch_awaitable = _get_py_core_async_cursor(cursor).fetchmany()
|
|
100
|
+
else:
|
|
101
|
+
fetch_awaitable = _get_py_core_async_cursor(cursor).fetchmany(size)
|
|
102
|
+
try:
|
|
103
|
+
with translate_py_core_exceptions():
|
|
104
|
+
rows = await fetch_awaitable
|
|
105
|
+
except (Exception, asyncio.CancelledError) as error:
|
|
106
|
+
_reconcile_failed_fetch(cursor, snapshot[0], "fetchmany", error)
|
|
107
|
+
raise
|
|
108
|
+
cursor._record_fetch(snapshot[0], len(rows), not rows) # pyright: ignore[reportPrivateUsage]
|
|
109
|
+
logger.debug(
|
|
110
|
+
"AsyncCursor.fetchmany: completed; row_count=%d; rowcount=%d",
|
|
111
|
+
len(rows),
|
|
112
|
+
cursor.rowcount,
|
|
113
|
+
)
|
|
114
|
+
return [_wrap_row(snapshot, row) for row in rows]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def fetchall(cursor: "_AsyncCursor") -> list[Row]:
|
|
118
|
+
"""Fetch all remaining rows through the py-core async cursor."""
|
|
119
|
+
logger.debug("AsyncCursor.fetchall: starting")
|
|
120
|
+
await cursor._wait_for_result_publication() # pyright: ignore[reportPrivateUsage]
|
|
121
|
+
snapshot = _snapshot_result(cursor)
|
|
122
|
+
try:
|
|
123
|
+
with translate_py_core_exceptions():
|
|
124
|
+
rows = await _get_py_core_async_cursor(cursor).fetchall()
|
|
125
|
+
except (Exception, asyncio.CancelledError) as error:
|
|
126
|
+
_reconcile_failed_fetch(cursor, snapshot[0], "fetchall", error)
|
|
127
|
+
raise
|
|
128
|
+
cursor._record_fetch(snapshot[0], len(rows), not rows) # pyright: ignore[reportPrivateUsage]
|
|
129
|
+
logger.debug(
|
|
130
|
+
"AsyncCursor.fetchall: completed; row_count=%d; rowcount=%d",
|
|
131
|
+
len(rows),
|
|
132
|
+
cursor.rowcount,
|
|
133
|
+
)
|
|
134
|
+
return [_wrap_row(snapshot, row) for row in rows]
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Translate mssql-py-core failures to the public DB-API exception hierarchy."""
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
|
|
6
|
+
from ..exceptions import (
|
|
7
|
+
DataError,
|
|
8
|
+
DatabaseError,
|
|
9
|
+
Error,
|
|
10
|
+
IntegrityError,
|
|
11
|
+
InterfaceError,
|
|
12
|
+
InternalError,
|
|
13
|
+
NotSupportedError,
|
|
14
|
+
OperationalError,
|
|
15
|
+
ProgrammingError,
|
|
16
|
+
Warning,
|
|
17
|
+
)
|
|
18
|
+
from ..logging import logger
|
|
19
|
+
|
|
20
|
+
_EXCEPTION_TYPES = {
|
|
21
|
+
"Warning": Warning,
|
|
22
|
+
"Error": Error,
|
|
23
|
+
"InterfaceError": InterfaceError,
|
|
24
|
+
"DatabaseError": DatabaseError,
|
|
25
|
+
"DataError": DataError,
|
|
26
|
+
"OperationalError": OperationalError,
|
|
27
|
+
"IntegrityError": IntegrityError,
|
|
28
|
+
"InternalError": InternalError,
|
|
29
|
+
"ProgrammingError": ProgrammingError,
|
|
30
|
+
"NotSupportedError": NotSupportedError,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
_ASYNC_DRIVER_ERROR = "Async operation failed"
|
|
34
|
+
|
|
35
|
+
_PROGRAMMING_RUNTIME_ERRORS = ("Cursor is closed",)
|
|
36
|
+
_INTERFACE_RUNTIME_ERRORS = (
|
|
37
|
+
"Connection is closing",
|
|
38
|
+
"Connection is closed",
|
|
39
|
+
)
|
|
40
|
+
_OPERATIONAL_RUNTIME_ERROR_PREFIXES = (
|
|
41
|
+
"Connection is broken",
|
|
42
|
+
"Connection is busy",
|
|
43
|
+
)
|
|
44
|
+
_PROGRAMMING_TYPE_ERROR_PREFIXES = (
|
|
45
|
+
"The SQL contains ",
|
|
46
|
+
"Parameter style mismatch:",
|
|
47
|
+
"Named parameter cannot be empty",
|
|
48
|
+
)
|
|
49
|
+
_DATA_ERROR_NUMBERS = {241, 245, 248, 8114, 8115, 8134, 8152, 2628}
|
|
50
|
+
_INTEGRITY_ERROR_NUMBERS = {515, 547, 2601, 2627}
|
|
51
|
+
_PROGRAMMING_ERROR_NUMBERS = {102, 156, 201, 207, 208, 2714, 2812, 8144}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _translate_known_builtin_error(error: Exception) -> Exception:
|
|
55
|
+
message = str(error)
|
|
56
|
+
if isinstance(error, RuntimeError):
|
|
57
|
+
if message in _PROGRAMMING_RUNTIME_ERRORS:
|
|
58
|
+
return ProgrammingError(_ASYNC_DRIVER_ERROR, message)
|
|
59
|
+
if message in _INTERFACE_RUNTIME_ERRORS:
|
|
60
|
+
return InterfaceError(_ASYNC_DRIVER_ERROR, message)
|
|
61
|
+
if message.startswith(_OPERATIONAL_RUNTIME_ERROR_PREFIXES):
|
|
62
|
+
return OperationalError(_ASYNC_DRIVER_ERROR, message)
|
|
63
|
+
if isinstance(error, TypeError) and message.startswith(_PROGRAMMING_TYPE_ERROR_PREFIXES):
|
|
64
|
+
return ProgrammingError(_ASYNC_DRIVER_ERROR, message)
|
|
65
|
+
return error
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _classify_database_error(error: Exception, default_type: type[DatabaseError]):
|
|
69
|
+
diagnostics = getattr(error, "sql_errors", ())
|
|
70
|
+
numbers = {item.get("number") for item in diagnostics if isinstance(item, dict)}
|
|
71
|
+
if numbers & _DATA_ERROR_NUMBERS:
|
|
72
|
+
return DataError
|
|
73
|
+
if numbers & _INTEGRITY_ERROR_NUMBERS:
|
|
74
|
+
return IntegrityError
|
|
75
|
+
if numbers & _PROGRAMMING_ERROR_NUMBERS:
|
|
76
|
+
return ProgrammingError
|
|
77
|
+
return default_type
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def translate_py_core_exception(error: Exception) -> Exception:
|
|
81
|
+
"""Translate py-core and recognized built-in errors to public exceptions."""
|
|
82
|
+
for error_type in type(error).__mro__:
|
|
83
|
+
if error_type.__module__ != "mssql_py_core":
|
|
84
|
+
continue
|
|
85
|
+
public_type = _EXCEPTION_TYPES.get(error_type.__name__)
|
|
86
|
+
if public_type is None:
|
|
87
|
+
continue
|
|
88
|
+
if public_type is DatabaseError:
|
|
89
|
+
public_type = _classify_database_error(error, public_type)
|
|
90
|
+
|
|
91
|
+
logger.debug(
|
|
92
|
+
"Async exception translation: %s -> %s",
|
|
93
|
+
error_type.__name__,
|
|
94
|
+
public_type.__name__,
|
|
95
|
+
)
|
|
96
|
+
translated = public_type(_ASYNC_DRIVER_ERROR, str(error))
|
|
97
|
+
for attribute in ("sql_errors", "info_messages"):
|
|
98
|
+
if hasattr(error, attribute):
|
|
99
|
+
setattr(translated, attribute, getattr(error, attribute))
|
|
100
|
+
return translated
|
|
101
|
+
|
|
102
|
+
return _translate_known_builtin_error(error)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@contextmanager
|
|
106
|
+
def translate_py_core_exceptions() -> Iterator[None]:
|
|
107
|
+
"""Translate py-core and recognized built-in errors raised by the wrapped operation."""
|
|
108
|
+
try:
|
|
109
|
+
yield
|
|
110
|
+
except Exception as error:
|
|
111
|
+
translated = translate_py_core_exception(error)
|
|
112
|
+
if translated is error:
|
|
113
|
+
raise
|
|
114
|
+
raise translated from error
|