Skip to content

CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries#663

Open
jahnvi480 wants to merge 10 commits into
mainfrom
jahnvi/odbc-package-split
Open

CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries#663
jahnvi480 wants to merge 10 commits into
mainfrom
jahnvi/odbc-package-split

Conversation

@jahnvi480

@jahnvi480 jahnvi480 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

AB#46217


Summary

Splits the ODBC driver binaries out of mssql-python into a new standalone, pure-data package mssql-python-odbc (import name mssql_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 driver libs/ tree and exposes get_driver_path() and get_libs_dir() with the same per-platform layout the C++ loader expects.
  • setup_odbc.py - builds the mssql-python-odbc wheels. 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 new GetOdbcLibsBaseDir() helper, which imports mssql_python_odbc when present and falls back to the bundled mssql_python libs 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 local mssql_python_odbc/libs/ build copies and egg-info.

VC++ runtime (vcredist) on Windows

The 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:

  • The main mssql-python wheel also ships the VC++ runtime, just relocated: build.bat copies msvcp140.dll out of vcredist/ to the package root next to the compiled ddbc_bindings extension, and setup.py then excludes the now-duplicate vcredist/ folder. That exclusion is de-duplication, not a removal of the runtime.
  • This package has no compiled extension, so there is no package-root .pyd to place the runtime beside. Keeping msvcp140.dll in its original vcredist/ folder keeps the driver binaries and the runtime they were built against together, and keeps the wheel self-contained.
  • No Phase-2 runtime skew: msodbcsql18.dll's dependency on msvcp140.dll is satisfied in-process by the mssql-python extension (built /MD, which loads msvcp140.dll from beside the .pyd), so the external-package and bundled-libs paths behave identically. This copy is completeness + attribution, which is why GetOdbcLibsBaseDir()'s completeness check verifies the driver + mssql-auth.dll but not vcredist.

Phasing (no breaking change in this PR)

  • Phase 2 (this PR): mssql-python prefers the external package but keeps bundling libs/ as a fallback, so existing installs keep working.
  • Phase 3 (future, v2.0.0): remove the bundled libs/ and depend on mssql-python-odbc exclusively.

Testing

  • test_024 parity suite: 7 passed, 1 skipped.
  • Built mssql_python_odbc-18.6.0-py3-none-win_amd64.whl locally: correct wheel tag, Root-Is-Purelib: false, driver DLLs present, vcredist (msvcp140.dll + license) present, twine check passes.

Notes

Companion PR (release pipelines): #664. Infra registration (ESRP onboarding of mssql-python-odbc, ADO build-definition id) is tracked there.

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.
Copilot AI review requested due to automatic review settings July 7, 2026 14:55
@jahnvi480 jahnvi480 changed the title Add standalone mssql-python-odbc package for ODBC driver binaries CHOR: Add standalone mssql-python-odbc package for ODBC driver binaries Jul 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_odbc package with get_driver_path() / get_libs_dir() and platform/arch/distro detection mirroring the C++ resolver.
  • Add setup_odbc.py to build platform-specific, Python-agnostic wheels for the ODBC-binaries package, including a local “sync libs” convenience.
  • Update ddbc_bindings.cpp to resolve the ODBC libs base dir via mssql_python_odbc when installed, with fallback to bundled mssql_python libs; 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.

Comment thread setup_odbc.py Outdated
Comment thread mssql_python/pybind/ddbc_bindings.cpp
Comment thread tests/test_024_odbc_package_split.py Outdated
Comment thread mssql_python_odbc/__init__.py
@jahnvi480 jahnvi480 changed the title CHOR: Add standalone mssql-python-odbc package for ODBC driver binaries CHORE: Add standalone mssql-python-odbc package for ODBC driver binaries Jul 7, 2026
@github-actions github-actions Bot added the pr-size: large Substantial code update label Jul 7, 2026
@jahnvi480 jahnvi480 marked this pull request as draft July 7, 2026 15:23
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

51%


🎯 Overall Coverage

80%


📈 Total Lines Covered: 6759 out of 8369
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/pybind/ddbc_bindings.cpp (51.4%): Missing lines 1136-1138,1142,1146-1148,1151-1153,1175-1176,1182,1195-1198

Summary

  • Total: 35 lines
  • Missing: 17 lines
  • Coverage: 51%

mssql_python/pybind/ddbc_bindings.cpp

Lines 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         }


📋 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

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

jahnvi480 added 4 commits July 7, 2026 21:34
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.
jahnvi480 added a commit that referenced this pull request Jul 8, 2026
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.
@jahnvi480 jahnvi480 marked this pull request as ready for review July 8, 2026 07:49
jahnvi480 and others added 3 commits July 8, 2026 14:27
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants