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.
@@ -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