CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries#663
CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries#663jahnvi480 wants to merge 13 commits into
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.
📊 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 Windows
1133 // `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 cannot
1139 // trigger the partially-initialized-module circular import that motivated
1140 // resolving these paths in C++ in the first place.
1141 //
! 1142 // (`GetDriverPathCpp` is defined further below; forward-declared here so we can
1143 // verify the external package actually ships this platform's driver binary.)
1144 std::string GetDriverPathCpp(const std::string& moduleDir);
1145
! 1146 std::string GetOdbcLibsBaseDir() {
! 1147 namespace fs = std::filesystem;
! 1148 // This function calls into the Python C-API (py::module::import, attribute
1149 // access, casts), so it must run with the GIL held. It is a no-op when the
1150 // 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;
1155 try {
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 a
1172 // usable base dir consistent with what the loader below actually needs,
1173 // so we never select a directory that would later make the loader throw
1174 // (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()));
1177 bool externalComplete = fs::exists(externalDriver, ec);
1178 #ifdef _WIN32
1179 if (externalComplete) {
1180 fs::path externalAuthDll = externalDriver.parent_path() / "mssql-auth.dll";Lines 1178-1186 1178 #ifdef _WIN32
1179 if (externalComplete) {
1180 fs::path externalAuthDll = externalDriver.parent_path() / "mssql-auth.dll";
1181 externalComplete = fs::exists(externalAuthDll, ec);
! 1182 }
1183 #endif
1184 if (externalComplete) {
1185 LOG("GetOdbcLibsBaseDir: Using external mssql_python_odbc package - directory='%s'",
1186 parentDir.string().c_str());Lines 1191-1202 1191 parentDir.string().c_str());
1192 return GetModuleDirectory();
1193 } catch (const py::error_already_set& e) {
1194 if (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.
! 1198 LOG("GetOdbcLibsBaseDir: mssql_python_odbc not installed (%s); "
1199 "falling back to bundled libs in mssql_python",
1200 e.what());
1201 return GetModuleDirectory();
1202 }Lines 1327-1335 1327 platform = "windows";
1328 // Normalize x86_64 to x64 for Windows naming
1329 if (arch == "x86_64")
1330 arch = "x64";
! 1331 fs::path driverPath =
1332 basePath / "libs" / platform / arch / ("msodbcsql" MSODBCSQL_VERSION_MAJOR ".dll");
1333 return driverPath.string();
1334
1335 #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.
bewithgaurav
left a comment
There was a problem hiding this comment.
some duplicacies are there & need to be resolved, comments added inline
requesting changes
…plit # Conflicts: # tests/test_000_dependencies.py
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.