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,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright (c) Microsoft Corporation.
|
|
3
|
+
Licensed under the MIT license.
|
|
4
|
+
Selects which ODBC provider (native driver package) mssql-python loads.
|
|
5
|
+
|
|
6
|
+
Two providers are supported: ``msodbcsql18`` (the Microsoft ODBC Driver 18,
|
|
7
|
+
shipped by ``mssql_python_odbc``) and ``mssql-odbc`` (the Rust driver, shipped
|
|
8
|
+
inside ``mssql_py_core`` / the ``mssql-python-rs`` wheel). Selection is process-wide and resolved exactly
|
|
9
|
+
once, before the native driver loads, from — in precedence order — the
|
|
10
|
+
``MSSQL_PYTHON_NATIVE_PROVIDER`` environment variable, the ``mssql_python.native_provider``
|
|
11
|
+
module property, then the release default. An unknown value fails closed rather
|
|
12
|
+
than falling back.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import threading
|
|
17
|
+
import warnings
|
|
18
|
+
import importlib
|
|
19
|
+
from typing import Dict, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
from mssql_python.logging import logger
|
|
22
|
+
|
|
23
|
+
NATIVE_PROVIDER_ENV_VAR = "MSSQL_PYTHON_NATIVE_PROVIDER"
|
|
24
|
+
|
|
25
|
+
# Customer-facing provider identifiers.
|
|
26
|
+
PROVIDER_MSODBCSQL18 = "msodbcsql18"
|
|
27
|
+
PROVIDER_MSSQL_ODBC = "mssql-odbc"
|
|
28
|
+
|
|
29
|
+
# Phase 1 default. Phase 2 flips this to PROVIDER_MSSQL_ODBC via a documented release.
|
|
30
|
+
_DEFAULT_PROVIDER = PROVIDER_MSODBCSQL18
|
|
31
|
+
|
|
32
|
+
# Provider -> import package that ships its native binaries.
|
|
33
|
+
# Keep in sync with ddbc_bindings.cpp's ProviderPackageForId / ProviderDistForId.
|
|
34
|
+
_PACKAGE_BY_PROVIDER: Dict[str, str] = {
|
|
35
|
+
PROVIDER_MSODBCSQL18: "mssql_python_odbc",
|
|
36
|
+
PROVIDER_MSSQL_ODBC: "mssql_py_core",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Provider -> the pip distribution that installs its package (for error hints).
|
|
40
|
+
_DIST_BY_PROVIDER: Dict[str, str] = {
|
|
41
|
+
PROVIDER_MSODBCSQL18: "mssql-python-odbc",
|
|
42
|
+
PROVIDER_MSSQL_ODBC: "mssql-python-rs",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _normalize(value: str) -> str:
|
|
47
|
+
"""Return the canonical provider id for ``value`` or raise ``ValueError``.
|
|
48
|
+
|
|
49
|
+
An unrecognized selection is rejected so a typo fails closed instead of
|
|
50
|
+
silently loading the default provider.
|
|
51
|
+
"""
|
|
52
|
+
canonical = value.strip().lower()
|
|
53
|
+
if canonical not in _PACKAGE_BY_PROVIDER:
|
|
54
|
+
valid = ", ".join(sorted(_PACKAGE_BY_PROVIDER))
|
|
55
|
+
raise ValueError(f"Unknown ODBC provider {value!r}. Valid providers are: {valid}.")
|
|
56
|
+
return canonical
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ProviderManager:
|
|
60
|
+
"""Process-wide, resolve-once selector for the ODBC provider.
|
|
61
|
+
|
|
62
|
+
The selection freezes when :meth:`resolve` first runs (at native driver
|
|
63
|
+
load). A later change to the module property is ignored with a warning,
|
|
64
|
+
mirroring the connection-pool configuration model.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
_lock: threading.Lock = threading.Lock()
|
|
68
|
+
_property_value: Optional[str] = None
|
|
69
|
+
_resolved: Optional[str] = None
|
|
70
|
+
_source: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def _compute(cls) -> Tuple[str, str]:
|
|
74
|
+
"""Apply precedence env var -> module property -> default (lock-free)."""
|
|
75
|
+
env_value = os.environ.get(NATIVE_PROVIDER_ENV_VAR)
|
|
76
|
+
if env_value and env_value.strip():
|
|
77
|
+
return _normalize(env_value), "environment"
|
|
78
|
+
if cls._property_value is not None:
|
|
79
|
+
return cls._property_value, "property"
|
|
80
|
+
return _DEFAULT_PROVIDER, "default"
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def set_property(cls, value: Optional[str]) -> None:
|
|
84
|
+
"""Set the module-property selection.
|
|
85
|
+
|
|
86
|
+
Accepts a provider id or ``None`` to clear. A change after the provider
|
|
87
|
+
has been resolved is ignored with a warning; the env var still takes
|
|
88
|
+
precedence over this value when both are set.
|
|
89
|
+
"""
|
|
90
|
+
with cls._lock:
|
|
91
|
+
canonical = _normalize(value) if value is not None else None
|
|
92
|
+
if cls._resolved is not None:
|
|
93
|
+
if canonical != cls._resolved:
|
|
94
|
+
cls._warn_frozen()
|
|
95
|
+
return
|
|
96
|
+
cls._property_value = canonical
|
|
97
|
+
env_value = os.environ.get(NATIVE_PROVIDER_ENV_VAR)
|
|
98
|
+
if canonical is not None and env_value and env_value.strip():
|
|
99
|
+
try:
|
|
100
|
+
env_provider = _normalize(env_value)
|
|
101
|
+
except ValueError:
|
|
102
|
+
# Preserve the existing fail-closed error at connection time.
|
|
103
|
+
return
|
|
104
|
+
if canonical != env_provider:
|
|
105
|
+
cls._warn_env_override(canonical, env_provider)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def resolve(cls) -> str:
|
|
109
|
+
"""Resolve and freeze the provider, returning its canonical id."""
|
|
110
|
+
with cls._lock:
|
|
111
|
+
if cls._resolved is None:
|
|
112
|
+
cls._resolved, cls._source = cls._compute()
|
|
113
|
+
logger.info(
|
|
114
|
+
"ODBC provider resolved to '%s' (source=%s)",
|
|
115
|
+
cls._resolved,
|
|
116
|
+
cls._source,
|
|
117
|
+
)
|
|
118
|
+
return cls._resolved
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def effective(cls) -> str:
|
|
122
|
+
"""Return the provider that would be used, without freezing it.
|
|
123
|
+
|
|
124
|
+
Reports the release default for an invalid selection (e.g. a mistyped
|
|
125
|
+
env var) rather than raising - this backs the public getter and
|
|
126
|
+
diagnostics, which must stay safe to read at any time. The hard
|
|
127
|
+
failure for a bad selection surfaces at :meth:`resolve`/
|
|
128
|
+
:meth:`ensure_available` instead.
|
|
129
|
+
"""
|
|
130
|
+
with cls._lock:
|
|
131
|
+
if cls._resolved is not None:
|
|
132
|
+
return cls._resolved
|
|
133
|
+
try:
|
|
134
|
+
provider, _ = cls._compute()
|
|
135
|
+
except ValueError:
|
|
136
|
+
return _DEFAULT_PROVIDER
|
|
137
|
+
return provider
|
|
138
|
+
|
|
139
|
+
@classmethod
|
|
140
|
+
def package_name(cls, provider: Optional[str] = None) -> str:
|
|
141
|
+
"""Return the import package that ships ``provider``'s native binaries."""
|
|
142
|
+
provider = provider or cls.effective()
|
|
143
|
+
return _PACKAGE_BY_PROVIDER[provider]
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def ensure_available(cls) -> str:
|
|
147
|
+
"""Verify the selected provider's package is installed, then freeze it.
|
|
148
|
+
|
|
149
|
+
Called before the native driver loads. Fails closed with an actionable
|
|
150
|
+
error if the package is missing. The selection is only frozen (via
|
|
151
|
+
:meth:`resolve`) once the package has been confirmed importable, so a
|
|
152
|
+
failed check here does not permanently lock in a provider that never
|
|
153
|
+
actually loaded - a later call can still select a different, installed
|
|
154
|
+
provider instead of requiring a process restart.
|
|
155
|
+
"""
|
|
156
|
+
provider = cls.effective()
|
|
157
|
+
package = _PACKAGE_BY_PROVIDER[provider]
|
|
158
|
+
try:
|
|
159
|
+
importlib.import_module(package)
|
|
160
|
+
except ModuleNotFoundError as exc:
|
|
161
|
+
if exc.name != package:
|
|
162
|
+
# A transitive dependency of an installed package is missing,
|
|
163
|
+
# or the package is broken - don't mask it as "not installed".
|
|
164
|
+
raise
|
|
165
|
+
dist = _DIST_BY_PROVIDER[provider]
|
|
166
|
+
raise ImportError(
|
|
167
|
+
f"The '{provider}' ODBC provider is selected but its package "
|
|
168
|
+
f"'{package}' is not installed. Install it with: pip install {dist}"
|
|
169
|
+
) from exc
|
|
170
|
+
return cls.resolve()
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
def is_frozen(cls) -> bool:
|
|
174
|
+
"""Whether the provider has been resolved and can no longer change."""
|
|
175
|
+
return cls._resolved is not None
|
|
176
|
+
|
|
177
|
+
@classmethod
|
|
178
|
+
def get_info(cls) -> Dict[str, object]:
|
|
179
|
+
"""Report the selected provider for diagnostics.
|
|
180
|
+
|
|
181
|
+
Never raises: an invalid selection is reported via the ``error`` key
|
|
182
|
+
(with ``id`` falling back to the default) instead of propagating, so
|
|
183
|
+
this stays safe to call at any time, including before a provider is
|
|
184
|
+
chosen or resolvable.
|
|
185
|
+
"""
|
|
186
|
+
with cls._lock:
|
|
187
|
+
if cls._resolved is not None:
|
|
188
|
+
provider, source, error = cls._resolved, cls._source, None
|
|
189
|
+
else:
|
|
190
|
+
try:
|
|
191
|
+
provider, source = cls._compute()
|
|
192
|
+
error = None
|
|
193
|
+
except ValueError as exc:
|
|
194
|
+
provider, source, error = _DEFAULT_PROVIDER, None, str(exc)
|
|
195
|
+
frozen = cls._resolved is not None
|
|
196
|
+
|
|
197
|
+
version = None
|
|
198
|
+
driver_path = None
|
|
199
|
+
package = _PACKAGE_BY_PROVIDER[provider]
|
|
200
|
+
try:
|
|
201
|
+
provider_module = importlib.import_module(package)
|
|
202
|
+
version = getattr(provider_module, "__version__", None)
|
|
203
|
+
module_file = getattr(provider_module, "__file__", None)
|
|
204
|
+
if module_file:
|
|
205
|
+
from mssql_python import ddbc_bindings
|
|
206
|
+
|
|
207
|
+
driver_path = ddbc_bindings._get_odbc_driver_path(
|
|
208
|
+
os.path.dirname(os.path.abspath(module_file)), provider
|
|
209
|
+
)
|
|
210
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
211
|
+
# Diagnostics must remain safe even for a broken provider package.
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
info: Dict[str, object] = {
|
|
215
|
+
"id": provider,
|
|
216
|
+
"package": package,
|
|
217
|
+
"version": version,
|
|
218
|
+
"driver_path": driver_path,
|
|
219
|
+
"source": source,
|
|
220
|
+
"frozen": frozen,
|
|
221
|
+
}
|
|
222
|
+
if error is not None:
|
|
223
|
+
info["error"] = error
|
|
224
|
+
return info
|
|
225
|
+
|
|
226
|
+
@classmethod
|
|
227
|
+
def _warn_env_override(cls, requested: str, effective: str) -> None:
|
|
228
|
+
message = (
|
|
229
|
+
f"ODBC provider property was set to '{requested}', but "
|
|
230
|
+
f"{NATIVE_PROVIDER_ENV_VAR} selects '{effective}' and takes precedence."
|
|
231
|
+
)
|
|
232
|
+
logger.warning(message)
|
|
233
|
+
warnings.warn(message, RuntimeWarning, stacklevel=3)
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def _warn_frozen(cls) -> None:
|
|
237
|
+
message = (
|
|
238
|
+
f"ODBC provider is already loaded as '{cls._resolved}'; ignoring the "
|
|
239
|
+
f"change. Select a provider before the first connection, or set the "
|
|
240
|
+
f"{NATIVE_PROVIDER_ENV_VAR} environment variable."
|
|
241
|
+
)
|
|
242
|
+
logger.warning(message)
|
|
243
|
+
warnings.warn(message, RuntimeWarning, stacklevel=3)
|
|
244
|
+
|
|
245
|
+
@classmethod
|
|
246
|
+
def _reset_for_testing(cls) -> None:
|
|
247
|
+
"""Reset selection state - for testing purposes only."""
|
|
248
|
+
with cls._lock:
|
|
249
|
+
cls._property_value = None
|
|
250
|
+
cls._resolved = None
|
|
251
|
+
cls._source = None
|