arrowsql 0.1.0__cp313-cp313-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.
Files changed (45) hide show
  1. arrowsql/__init__.py +653 -0
  2. arrowsql/_rust_auth.py +170 -0
  3. arrowsql/_server_sqlstate.py +11 -0
  4. arrowsql/_utf8_strings.py +78 -0
  5. arrowsql/agent.py +620 -0
  6. arrowsql/aio.py +533 -0
  7. arrowsql/arrow_parallel.py +464 -0
  8. arrowsql/async_query/__init__.py +40 -0
  9. arrowsql/async_query/_connection_context.py +39 -0
  10. arrowsql/async_query/_native.py +24 -0
  11. arrowsql/async_query/async_connection.py +160 -0
  12. arrowsql/async_query/async_cursor.py +225 -0
  13. arrowsql/async_query/async_execute.py +113 -0
  14. arrowsql/async_query/async_fetch.py +153 -0
  15. arrowsql/async_query/exception_translator.py +132 -0
  16. arrowsql/auth.py +913 -0
  17. arrowsql/bulk_parallel.py +385 -0
  18. arrowsql/connection.py +2377 -0
  19. arrowsql/connection_string_builder.py +114 -0
  20. arrowsql/connection_string_parser.py +417 -0
  21. arrowsql/constants.py +701 -0
  22. arrowsql/cursor.py +4516 -0
  23. arrowsql/db_connection.py +98 -0
  24. arrowsql/ddbc_bindings.cp313-win_amd64.pyd +0 -0
  25. arrowsql/ddbc_bindings.pyi +283 -0
  26. arrowsql/decimal_config.py +98 -0
  27. arrowsql/exceptions.py +378 -0
  28. arrowsql/helpers.py +436 -0
  29. arrowsql/logging.py +700 -0
  30. arrowsql/mssql_python.pyi +579 -0
  31. arrowsql/odbc_provider.py +269 -0
  32. arrowsql/parameter_helper.py +442 -0
  33. arrowsql/perf_timer.py +286 -0
  34. arrowsql/pooling.py +147 -0
  35. arrowsql/py.typed +1 -0
  36. arrowsql/row.py +462 -0
  37. arrowsql/type.py +160 -0
  38. arrowsql-0.1.0.dist-info/METADATA +146 -0
  39. arrowsql-0.1.0.dist-info/RECORD +45 -0
  40. arrowsql-0.1.0.dist-info/WHEEL +4 -0
  41. arrowsql-0.1.0.dist-info/entry_points.txt +2 -0
  42. arrowsql-0.1.0.dist-info/licenses/LICENSE +21 -0
  43. arrowsql-0.1.0.dist-info/licenses/LICENSE.upstream-mssql-python +47 -0
  44. arrowsql-0.1.0.dist-info/sboms/ddbc_bindings.cyclonedx.json +5195 -0
  45. mssql_python/__init__.py +39 -0
@@ -0,0 +1,269 @@
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 arrowsql.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 _rust_port() -> bool:
47
+ """Whether the loaded bindings are the Rust ones (looked up once)."""
48
+ global _RUST_PORT
49
+ if _RUST_PORT is None:
50
+ from arrowsql import ddbc_bindings
51
+
52
+ _RUST_PORT = bool(getattr(ddbc_bindings, "__rust_port__", False))
53
+ return _RUST_PORT
54
+
55
+
56
+ _RUST_PORT: Optional[bool] = None
57
+
58
+
59
+ def _normalize(value: str) -> str:
60
+ """Return the canonical provider id for ``value`` or raise ``ValueError``.
61
+
62
+ An unrecognized selection is rejected so a typo fails closed instead of
63
+ silently loading the default provider.
64
+ """
65
+ canonical = value.strip().lower()
66
+ if canonical not in _PACKAGE_BY_PROVIDER:
67
+ valid = ", ".join(sorted(_PACKAGE_BY_PROVIDER))
68
+ raise ValueError(f"Unknown ODBC provider {value!r}. Valid providers are: {valid}.")
69
+ return canonical
70
+
71
+
72
+ class ProviderManager:
73
+ """Process-wide, resolve-once selector for the ODBC provider.
74
+
75
+ The selection freezes when :meth:`resolve` first runs (at native driver
76
+ load). A later change to the module property is ignored with a warning,
77
+ mirroring the connection-pool configuration model.
78
+ """
79
+
80
+ _lock: threading.Lock = threading.Lock()
81
+ _property_value: Optional[str] = None
82
+ _resolved: Optional[str] = None
83
+ _source: Optional[str] = None
84
+
85
+ @classmethod
86
+ def _compute(cls) -> Tuple[str, str]:
87
+ """Apply precedence env var -> module property -> default (lock-free)."""
88
+ env_value = os.environ.get(NATIVE_PROVIDER_ENV_VAR)
89
+ if env_value and env_value.strip():
90
+ return _normalize(env_value), "environment"
91
+ if cls._property_value is not None:
92
+ return cls._property_value, "property"
93
+ return _DEFAULT_PROVIDER, "default"
94
+
95
+ @classmethod
96
+ def set_property(cls, value: Optional[str]) -> None:
97
+ """Set the module-property selection.
98
+
99
+ Accepts a provider id or ``None`` to clear. A change after the provider
100
+ has been resolved is ignored with a warning; the env var still takes
101
+ precedence over this value when both are set.
102
+ """
103
+ with cls._lock:
104
+ canonical = _normalize(value) if value is not None else None
105
+ if cls._resolved is not None:
106
+ if canonical != cls._resolved:
107
+ cls._warn_frozen()
108
+ return
109
+ cls._property_value = canonical
110
+ env_value = os.environ.get(NATIVE_PROVIDER_ENV_VAR)
111
+ if canonical is not None and env_value and env_value.strip():
112
+ try:
113
+ env_provider = _normalize(env_value)
114
+ except ValueError:
115
+ # Preserve the existing fail-closed error at connection time.
116
+ return
117
+ if canonical != env_provider:
118
+ cls._warn_env_override(canonical, env_provider)
119
+
120
+ @classmethod
121
+ def resolve(cls) -> str:
122
+ """Resolve and freeze the provider, returning its canonical id."""
123
+ with cls._lock:
124
+ if cls._resolved is None:
125
+ cls._resolved, cls._source = cls._compute()
126
+ logger.info(
127
+ "ODBC provider resolved to '%s' (source=%s)",
128
+ cls._resolved,
129
+ cls._source,
130
+ )
131
+ return cls._resolved
132
+
133
+ @classmethod
134
+ def effective(cls) -> str:
135
+ """Return the provider that would be used, without freezing it.
136
+
137
+ Reports the release default for an invalid selection (e.g. a mistyped
138
+ env var) rather than raising - this backs the public getter and
139
+ diagnostics, which must stay safe to read at any time. The hard
140
+ failure for a bad selection surfaces at :meth:`resolve`/
141
+ :meth:`ensure_available` instead.
142
+ """
143
+ with cls._lock:
144
+ if cls._resolved is not None:
145
+ return cls._resolved
146
+ try:
147
+ provider, _ = cls._compute()
148
+ except ValueError:
149
+ return _DEFAULT_PROVIDER
150
+ return provider
151
+
152
+ @classmethod
153
+ def package_name(cls, provider: Optional[str] = None) -> str:
154
+ """Return the import package that ships ``provider``'s native binaries."""
155
+ provider = provider or cls.effective()
156
+ return _PACKAGE_BY_PROVIDER[provider]
157
+
158
+ @classmethod
159
+ def ensure_available(cls) -> str:
160
+ """Verify the selected provider's package is installed, then freeze it.
161
+
162
+ Called before the native driver loads. Fails closed with an actionable
163
+ error if the package is missing. The selection is only frozen (via
164
+ :meth:`resolve`) once the package has been confirmed importable, so a
165
+ failed check here does not permanently lock in a provider that never
166
+ actually loaded - a later call can still select a different, installed
167
+ provider instead of requiring a process restart.
168
+ """
169
+ # RUST-PORT: the Rust bindings speak TDS natively; no ODBC driver
170
+ # package needs to be importable, and a frozen selection is final.
171
+ if _rust_port():
172
+ resolved = cls._resolved
173
+ return resolved if resolved is not None else cls.resolve()
174
+ provider = cls.effective()
175
+ package = _PACKAGE_BY_PROVIDER[provider]
176
+ try:
177
+ importlib.import_module(package)
178
+ except ModuleNotFoundError as exc:
179
+ if exc.name != package:
180
+ # A transitive dependency of an installed package is missing,
181
+ # or the package is broken - don't mask it as "not installed".
182
+ raise
183
+ dist = _DIST_BY_PROVIDER[provider]
184
+ raise ImportError(
185
+ f"The '{provider}' ODBC provider is selected but its package "
186
+ f"'{package}' is not installed. Install it with: pip install {dist}"
187
+ ) from exc
188
+ return cls.resolve()
189
+
190
+ @classmethod
191
+ def is_frozen(cls) -> bool:
192
+ """Whether the provider has been resolved and can no longer change."""
193
+ return cls._resolved is not None
194
+
195
+ @classmethod
196
+ def get_info(cls) -> Dict[str, object]:
197
+ """Report the selected provider for diagnostics.
198
+
199
+ Never raises: an invalid selection is reported via the ``error`` key
200
+ (with ``id`` falling back to the default) instead of propagating, so
201
+ this stays safe to call at any time, including before a provider is
202
+ chosen or resolvable.
203
+ """
204
+ with cls._lock:
205
+ if cls._resolved is not None:
206
+ provider, source, error = cls._resolved, cls._source, None
207
+ else:
208
+ try:
209
+ provider, source = cls._compute()
210
+ error = None
211
+ except ValueError as exc:
212
+ provider, source, error = _DEFAULT_PROVIDER, None, str(exc)
213
+ frozen = cls._resolved is not None
214
+
215
+ version = None
216
+ driver_path = None
217
+ package = _PACKAGE_BY_PROVIDER[provider]
218
+ try:
219
+ provider_module = importlib.import_module(package)
220
+ version = getattr(provider_module, "__version__", None)
221
+ module_file = getattr(provider_module, "__file__", None)
222
+ if module_file:
223
+ from arrowsql import ddbc_bindings
224
+
225
+ driver_path = ddbc_bindings._get_odbc_driver_path(
226
+ os.path.dirname(os.path.abspath(module_file)), provider
227
+ )
228
+ except Exception: # pylint: disable=broad-exception-caught
229
+ # Diagnostics must remain safe even for a broken provider package.
230
+ pass
231
+
232
+ info: Dict[str, object] = {
233
+ "id": provider,
234
+ "package": package,
235
+ "version": version,
236
+ "driver_path": driver_path,
237
+ "source": source,
238
+ "frozen": frozen,
239
+ }
240
+ if error is not None:
241
+ info["error"] = error
242
+ return info
243
+
244
+ @classmethod
245
+ def _warn_env_override(cls, requested: str, effective: str) -> None:
246
+ message = (
247
+ f"ODBC provider property was set to '{requested}', but "
248
+ f"{NATIVE_PROVIDER_ENV_VAR} selects '{effective}' and takes precedence."
249
+ )
250
+ logger.warning(message)
251
+ warnings.warn(message, RuntimeWarning, stacklevel=3)
252
+
253
+ @classmethod
254
+ def _warn_frozen(cls) -> None:
255
+ message = (
256
+ f"ODBC provider is already loaded as '{cls._resolved}'; ignoring the "
257
+ f"change. Select a provider before the first connection, or set the "
258
+ f"{NATIVE_PROVIDER_ENV_VAR} environment variable."
259
+ )
260
+ logger.warning(message)
261
+ warnings.warn(message, RuntimeWarning, stacklevel=3)
262
+
263
+ @classmethod
264
+ def _reset_for_testing(cls) -> None:
265
+ """Reset selection state - for testing purposes only."""
266
+ with cls._lock:
267
+ cls._property_value = None
268
+ cls._resolved = None
269
+ cls._source = None