Uh oh!
There was an error while loading. Please reload this page.
CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries - #663
Conversation
Introduce mssql_python_odbc, a pure-data sibling package (v18.6.0) that ships the ODBC driver libs/ tree, plus setup_odbc.py which builds platform-specific, Python-agnostic (py3-none-<plat>) wheels for the 7 supported platforms. ddbc_bindings.cpp now resolves the driver/libs base directory from mssql_python_odbc when installed, and falls back to the bundled mssql_python libs during the Phase-2 transition (bundled libs are retained). Add tests/test_024_odbc_package_split.py to verify Python/C++ driver-path parity, and .gitignore entries for local build copies.
There was a problem hiding this comment.
Pull request overview
Splits the bundled ODBC driver binaries out of mssql-python into a new standalone data-only package (mssql-python-odbc / mssql_python_odbc), updates the native loader to prefer the external package with a fallback, and adds parity tests to ensure Python and C++ resolve identical driver paths.
Changes:
- Add new
mssql_python_odbcpackage withget_driver_path()/get_libs_dir()and platform/arch/distro detection mirroring the C++ resolver. - Add
setup_odbc.pyto build platform-specific, Python-agnostic wheels for the ODBC-binaries package, including a local “sync libs” convenience. - Update
ddbc_bindings.cppto resolve the ODBC libs base dir viamssql_python_odbcwhen installed, with fallback to bundledmssql_pythonlibs; add tests validating Python/C++ path parity.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_024_odbc_package_split.py | Adds test coverage validating the new package’s API and Python/C++ driver-path parity. |
setup_odbc.py | Introduces a dedicated wheel build script for the new ODBC-binaries package and a transition-time libs sync. |
mssql_python/pybind/ddbc_bindings.cpp | Switches native driver/libs base-dir resolution to prefer mssql_python_odbc, falling back to bundled libs. |
mssql_python_odbc/__init__.py | Implements the new Python API for locating driver binaries within the standalone package. |
.gitignore | Ignores transition-time generated/copy artifacts for the new package (libs/, egg-info). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/pybind/ddbc_bindings.cppLines 1132-1157 1132// its directory as the base that `GetDriverPathCpp` (and the Windows1133// `mssql-auth.dll` lookup) append `libs` to.1134//1135// During the Phase-2 transition we fall back to the bundled `mssql_python`
! 1136// directory when the external package is not installed, so a wheel that still
! 1137// bundles `libs/` keeps working. Importing `mssql_python_odbc` here is
! 1138// Alpine/musl-safe precisely because it is a separate pure package: it cannot1139// trigger the partially-initialized-module circular import that motivated1140// resolving these paths in C++ in the first place.1141//
! 1142// (`GetDriverPathCpp` is defined further below; forward-declared here so we can1143// verify the external package actually ships this platform's driver binary.)1144 std::string GetDriverPathCpp(const std::string& moduleDir);
1145 ! 1146 std::string GetOdbcLibsBaseDir() {
! 1147namespacefs= std::filesystem;
! 1148// This function calls into the Python C-API (py::module::import, attribute1149// access, casts), so it must run with the GIL held. It is a no-op when the1150// GIL is already held — which it is at the sole current call site, during
! 1151// module initialization — but acquiring it here self-documents the C-API
! 1152// dependency and keeps a future GIL-released caller from turning this into a
! 1153// hard crash.1154 py::gil_scoped_acquire gil;
1155try {
1156 py::object module = py::module::import("mssql_python_odbc");
1157 py::object module_path = module.attr("__file__");Lines 1171-1180 1171// unconditionally. Verifying both here keeps this resolver's notion of a1172// usable base dir consistent with what the loader below actually needs,1173// so we never select a directory that would later make the loader throw1174// (e.g. a dir that has msodbcsql18.dll but is missing mssql-auth.dll).
! 1175 std::error_code ec;
! 1176 fs::path externalDriver(GetDriverPathCpp(parentDir.string()));
1177bool externalComplete = fs::exists(externalDriver, ec);
1178 #ifdef _WIN32
1179if (externalComplete) {
1180 fs::path externalAuthDll = externalDriver.parent_path() / "mssql-auth.dll";Lines 1178-1186 1178 #ifdef _WIN32
1179if (externalComplete) {
1180 fs::path externalAuthDll = externalDriver.parent_path() / "mssql-auth.dll";
1181 externalComplete = fs::exists(externalAuthDll, ec);
! 1182 }
1183 #endif
1184if (externalComplete) {
1185LOG("GetOdbcLibsBaseDir: Using external mssql_python_odbc package - directory='%s'",
1186 parentDir.string().c_str());Lines 1191-1202 1191 parentDir.string().c_str());
1192return GetModuleDirectory();
1193 } catch (const py::error_already_set& e) {
1194if (e.matches(PyExc_ModuleNotFoundError)) {
! 1195// Expected in Phase 2 when the standalone package is not installed.
! 1196// pybind11 has already fetched and cleared the CPython error
! 1197// indicator, so re-importing `mssql_python` below is safe.
! 1198LOG("GetOdbcLibsBaseDir: mssql_python_odbc not installed (%s); "1199"falling back to bundled libs in mssql_python",
1200 e.what());
1201returnGetModuleDirectory();
1202 }Lines 1327-1335 1327 platform = "windows";
1328// Normalize x86_64 to x64 for Windows naming1329if (arch == "x86_64")
1330 arch = "x64";
! 1331 fs::path driverPath =
1332 basePath / "libs" / platform / arch / ("msodbcsql"MSODBCSQL_VERSION_MAJOR".dll");
1333return driverPath.string();
13341335 #else📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.ddbc_bindings.cpp: 76.1%
mssql_python.pybind.connection.connection.cpp: 76.2%
mssql_python.__init__.py: 77.3%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.connection.py: 83.6%
mssql_python.logging.py: 85.5%🔗 Quick Links
|
…rsion coupling, guard minimum setuptools
Corrects the earlier 'parity' framing. Main does not strip the VC++ runtime: build.bat relocates msvcp140.dll next to the .pyd and setup.py excludes the duplicate vcredist/ folder (de-duplication). The odbc package has no .pyd, so it keeps msvcp140.dll in vcredist/ with its license. No Phase-2 skew: the driver's runtime dependency is satisfied in-process by the /MD-built extension in both paths.
Per review feedback, build and release the standalone mssql-python-odbc package from the existing Build-Release-Package-Pipeline (def 2199) and official release pipeline instead of separate ADO definitions. Build pipeline: add odbcWindows/Macos/LinuxConfigs params (2+1+4=7 data-only wheels), reference the 3 odbc stage templates alongside the mssql-python stages, and add a separate ConsolidateOdbc stage publishing drop_ConsolidateOdbc_ConsolidateArtifacts (distinct from mssql-python's drop_Consolidate_ConsolidateArtifacts). Release pipeline: add releasePackage parameter (mssql-python | mssql-python-odbc); select the consolidated artifact accordingly and skip symbol publishing + mssql-py-core version validation for odbc. Delete the redundant standalone orchestrators (build-release-odbc-pipeline.yml, official-release-odbc-pipeline.yml). This removes the need to register a new ADO build definition and the odbcBuildDefinitionId placeholder. Both packages now build from def 2199; the odbc stages consume setup_odbc.py from PR #663, so #663 must merge before these stages can build.
The comment above MIN_SETUPTOOLS claimed the libs/ tree is gitignored and that include_package_data cannot recover the binaries from version control. Both are inaccurate: the driver binaries are committed, and package_data globs enumerate files from the on-disk mssql_python_odbc/libs/ subtree, not from git. Replace with an accurate description; the real rationale for the guard (setuptools 62.3.0 '**' recursive-glob support) is retained.
Gaurav Sharma (bewithgaurav)
left a comment
There was a problem hiding this comment.
some duplicacies are there & need to be resolved, comments added inline
requesting changes
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…plit # Conflicts: # tests/test_000_dependencies.py
Per review feedback, build and release the standalone mssql-python-odbc package from the existing Build-Release-Package-Pipeline (def 2199) and official release pipeline instead of separate ADO definitions. Build pipeline: add odbcWindows/Macos/LinuxConfigs params (2+1+4=7 data-only wheels), reference the 3 odbc stage templates alongside the mssql-python stages, and add a separate ConsolidateOdbc stage publishing drop_ConsolidateOdbc_ConsolidateArtifacts (distinct from mssql-python's drop_Consolidate_ConsolidateArtifacts). Release pipeline: add releasePackage parameter (mssql-python | mssql-python-odbc); select the consolidated artifact accordingly and skip symbol publishing + mssql-py-core version validation for odbc. Delete the redundant standalone orchestrators (build-release-odbc-pipeline.yml, official-release-odbc-pipeline.yml). This removes the need to register a new ADO build definition and the odbcBuildDefinitionId placeholder. Both packages now build from def 2199; the odbc stages consume setup_odbc.py from PR #663, so #663 must merge before these stages can build.
Gaurav Sharma (bewithgaurav)
left a comment
There was a problem hiding this comment.
lgtm, lets do the version pinning once we're published and ready to link
Uh oh!
There was an error while loading. Please reload this page.
### Work Item / Issue Reference > [AB#46627](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/46627) ------------------------------------------------------------------- ### Summary Release mssql-python v1.12.0. Version bump to 1.12.0. Updates `mssql_python/__init__.py`, `setup.py`, and `PyPI_Description.md`. Bundled `mssql_py_core` bumped from 0.1.6 to 0.1.7. #### Enhancements - **Standalone `mssql-python-odbc` package** — ODBC driver binaries are now also published as a separate, pure-data companion package `mssql-python-odbc` (import name `mssql_python_odbc`, version 18.6.2). `mssql-python` declares it in `install_requires` and prefers the external package at import time, falling back to the still-bundled `libs/` tree so existing installs keep working (#663, #687). #### Bug Fixes - **Bulk copy connection timeout** — `cursor.bulkcopy()` now forwards the cursor's connection timeout (from `connect(timeout=X)`) into the underlying `mssql_py_core` connection, instead of always using the hardcoded 15s default (#650, issue #626). - **Bulk copy of custom CLR UDT columns** — Fixed `Protocol Error: Unsupported TDS type for bulk copy: 0xF0` on `cursor.bulkcopy()` into custom (non-spatial) CLR UDT columns; UDT columns are now mapped to `varbinary(max)` on the wire (#688, via `mssql_py_core` 0.1.7, issue #667). #### Version Bump - `mssql_python/__init__.py`: `__version__ = "1.12.0"` - `setup.py`: `version="1.12.0"` - `PyPI_Description.md`: `## What's new in v1.12.0` section refreshed.
Work Item / Issue Reference
Summary
Splits the ODBC driver binaries out of
mssql-pythoninto a new standalone, pure-data packagemssql-python-odbc(import namemssql_python_odbc, version 18.6.0). This is the code / packaging half of the split; the ADO release pipelines are in companion PR #664.What is included
mssql_python_odbc/__init__.py- pure-Python package (no native extension) that ships the ODBC driverlibs/tree and exposesget_driver_path()andget_libs_dir()with the same per-platform layout the C++ loader expects.setup_odbc.py- builds themssql-python-odbcwheels. Each wheel is platform-specific but Python-agnostic (py3-none-<platform>), so we ship 7 wheels total (Windows amd64/arm64, macOS universal2, manylinux_2_28 x86_64/aarch64, musllinux_1_2 x86_64/aarch64) instead of a per-Python matrix. On Windows the wheel intentionally ships the VC++ runtime (vcredist/msvcp140.dll+ license) alongside the driver binaries - see "VC++ runtime" below.mssql_python/pybind/ddbc_bindings.cpp-LoadDriverOrThrowException()now resolves the driver / libs base directory via a newGetOdbcLibsBaseDir()helper, which importsmssql_python_odbcwhen present and falls back to the bundledmssql_pythonlibs when it is not. The fallback is GIL-safe and Alpine/musl-safe.tests/test_024_odbc_package_split.py- verifies Python vs C++ driver-path parity..gitignore- ignores localmssql_python_odbc/libs/build copies and egg-info.VC++ runtime (
vcredist) on WindowsThe Windows wheel ships
libs/windows/<arch>/vcredist/msvcp140.dll(with its license). This is a deliberate, documented choice - not "parity" with the main wheel's file layout, and not a reversal of a licensing/size decision:mssql-pythonwheel also ships the VC++ runtime, just relocated:build.batcopiesmsvcp140.dllout ofvcredist/to the package root next to the compiledddbc_bindingsextension, andsetup.pythen excludes the now-duplicatevcredist/folder. That exclusion is de-duplication, not a removal of the runtime..pydto place the runtime beside. Keepingmsvcp140.dllin its originalvcredist/folder keeps the driver binaries and the runtime they were built against together, and keeps the wheel self-contained.msodbcsql18.dll's dependency onmsvcp140.dllis satisfied in-process by the mssql-python extension (built/MD, which loadsmsvcp140.dllfrom beside the.pyd), so the external-package and bundled-libs paths behave identically. This copy is completeness + attribution, which is whyGetOdbcLibsBaseDir()'s completeness check verifies the driver +mssql-auth.dllbut notvcredist.Phasing (no breaking change in this PR)
mssql-pythonprefers the external package but keeps bundlinglibs/as a fallback, so existing installs keep working.libs/and depend onmssql-python-odbcexclusively.Testing
test_024parity suite: 7 passed, 1 skipped.mssql_python_odbc-18.6.0-py3-none-win_amd64.whllocally: correct wheel tag,Root-Is-Purelib: false, driver DLLs present,vcredist(msvcp140.dll+ license) present,twine checkpasses.Notes
Companion PR (release pipelines): #664. Infra registration (ESRP onboarding of
mssql-python-odbc, ADO build-definition id) is tracked there.