REFACTOR: remove bundled ODBC driver from mssql-python wheel; require mssql-python-odbc (Phase 2) - #693
Open
Jahnvi Thakkar (jahnvi480) wants to merge 22 commits into
Open
REFACTOR: remove bundled ODBC driver from mssql-python wheel; require mssql-python-odbc (Phase 2)#693Jahnvi Thakkar (jahnvi480) wants to merge 22 commits into
Jahnvi Thakkar (jahnvi480) wants to merge 22 commits into
Conversation
…ython-odbc Phase 2 of the ODBC package split. Move the driver binaries from mssql_python/libs to mssql_python_odbc/libs so only the mssql-python-odbc distribution ships them. Remove the C++ bundled-libs fallback in GetOdbcLibsBaseDir(): a missing or incomplete mssql_python_odbc now raises a clear error instead of silently using bundled libs. Drop libs packaging from setup.py, retire sync_libs() in setup_odbc.py, and repoint build.bat vcredist source. AB#46586
Piece A: setup_odbc.py builds all 7 py3-none-<platform> ODBC data wheels from one host via ODBC_TARGET_* overrides; per-target package_data (include_package_data=False) so each wheel ships only its own driver + LICENSING; plat_name_supplied=True fixes macOS wheel tagging. Piece B: one unconditional ODBC_BuildAll + ConsolidateOdbc stage runs first; Windows/macOS/Linux mssql-python build stages dependsOn ConsolidateOdbc and install the external mssql-python-odbc wheel before pytest (Linux via PIP_FIND_LINKS into the bind-mounted repo dir).
The mssql-python build stages now install the external mssql-python-odbc wheel and run the full pytest suite, so the separate post-consolidation wheel-installation test stages are redundant.
…cOS-14 agent The pure-data ODBC wheel was tagged macosx_15_0_universal2, which pip rejects on the macOS-14 build/test agent (from versions: none). Lower it to the canonical universal2 floor (macosx_11_0); the driver dylibs run on macOS 11+ and the tag stays compatible everywhere the mssql-python wheel installs.
Phase 2 moved the bundled ODBC driver libs from mssql_python into the external mssql_python_odbc package. test_libs_directory_exists and test_auth_dll_exists_if_libs_present now resolve the libs/ base dir via a helper that prefers mssql_python_odbc (falling back to mssql_python for the pre-split layout).
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/pybind/ddbc_bindings.cppLines 1160-1168 1160 if (!externalComplete) {
1161 LOG("GetOdbcLibsBaseDir: mssql_python_odbc present at '%s' but its ODBC driver "
1162 "binaries are missing or incomplete for this platform",
1163 parentDir.string().c_str());
! 1164 ThrowStdException(
1165 "The 'mssql-python-odbc' package is installed but its ODBC driver binaries "
1166 "are missing or incomplete for this platform. Reinstall it with: "
1167 "pip install --force-reinstall mssql-python-odbc");
1168 }Lines 1174-1184 1174 // Phase 2: the standalone package is required. Turn the missing
1175 // dependency into a clear, actionable error instead of a fallback.
1176 LOG("GetOdbcLibsBaseDir: required package mssql_python_odbc is not installed (%s)",
1177 e.what());
! 1178 ThrowStdException(
! 1179 "The required 'mssql-python-odbc' package (which ships the ODBC driver "
! 1180 "binaries) is not installed. Install it with: pip install mssql-python-odbc");
1181 }
1182 // A different import-time error means the package is installed but
1183 // broken; surface it instead of silently masking the real problem.
1184 LOG("GetOdbcLibsBaseDir: importing mssql_python_odbc failed unexpectedly (%s); "Lines 1472-1482 1472 // the actionable "install mssql-python-odbc" error into a hard crash. So we
1473 // capture any failure as an exception_ptr inside the callable and rethrow it
1474 // below, in a normal context that pybind11 can translate into a Python
1475 // exception. The stored error persists, so every subsequent call re-raises the
! 1476 // same actionable message instead of silently proceeding without a driver.
1477 std::call_once(m_onceFlag, [this]() {
! 1478 try {
1479 LoadDriverOrThrowException();
1480 m_driverLoaded = true;
1481 } catch (...) {
1482 m_loadError = std::current_exception();Lines 1482-1491 1482 m_loadError = std::current_exception();
1483 }
1484 });
1485 if (m_loadError) {
! 1486 std::rethrow_exception(m_loadError);
! 1487 }
1488 }
1489
1490 // SqlHandle definition
1491 SqlHandle::SqlHandle(SQLSMALLINT type, SQLHANDLE rawHandle) : _type(type), _handle(rawHandle) {}📋 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.6%
mssql_python.__init__.py: 77.6%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 81.4%
mssql_python.pybind.connection.connection.cpp: 84.3%
mssql_python.logging.py: 85.5%🔗 Quick Links
|
…ibs after Phase 2 move
Copilot stopped reviewing on behalf of
Jahnvi Thakkar (jahnvi480) due to an error
July 31, 2026 08:56
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR finalizes the Phase 2 split of the bundled ODBC driver into the standalone mssql-python-odbc package, makes the external package required at runtime, and updates build/test pipelines to produce and consume the new wheel layout.
Changes:
- Make
mssql_python_odbcrequired for driver resolution (no fallback to bundledmssql_python/libs), with clearer runtime errors. - Update ODBC wheel packaging to include only the target platform’s
libs/subtree and enable single-host cross-building of all ODBC wheels. - Refactor CI pipelines to build all ODBC wheels in one Windows stage, install the ODBC wheel before pytest in platform builds, and remove now-redundant “both wheels” installation test stages.
Reviewed changes
Copilot reviewed 17 out of 85 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/test_015_utf8_path_handling.py | Updates tests to locate driver libs in mssql_python_odbc (but still includes a fallback). |
| setup_odbc.py | Packages only the target platform’s ODBC libs/ subtree; adds env overrides for cross-building and forces wheel to trust supplied platform tags. |
| setup.py | Removes inclusion/exclusion rules for bundled libs/ since the driver is now shipped separately. |
| mssql_python/pybind/ddbc_bindings.cpp | Requires mssql_python_odbc and throws actionable errors if missing/incomplete; removes fallback behavior. |
| mssql_python/pybind/build.bat | Copies VC++ runtime from mssql_python_odbc/libs/.../vcredist instead of mssql_python/libs. |
| eng/pipelines/pr-validation-pipeline.yml | Updates validation paths to the new mssql_python_odbc/libs/... locations. |
| OneBranchPipelines/stages/wheel-installation-test-windows-stage.yml | Deleted: prior end-user “both wheels” installation test stage (Windows). |
| OneBranchPipelines/stages/wheel-installation-test-stage.yml | Deleted: prior end-user “both wheels” installation test stage (Linux). |
| OneBranchPipelines/stages/wheel-installation-test-macos-stage.yml | Deleted: prior end-user “both wheels” installation test stage (macOS). |
| OneBranchPipelines/stages/build-windows-single-stage.yml | Adds optional dependency/install of consolidated mssql-python-odbc wheel before pytest. |
| OneBranchPipelines/stages/build-odbc-windows-stage.yml | Deleted: replaced by a single-host “build all ODBC wheels” stage. |
| OneBranchPipelines/stages/build-odbc-macos-stage.yml | Deleted: replaced by a single-host “build all ODBC wheels” stage. |
| OneBranchPipelines/stages/build-odbc-linux-stage.yml | Deleted: replaced by a single-host “build all ODBC wheels” stage. |
| OneBranchPipelines/stages/build-odbc-all-stage.yml | New: builds all 7 ODBC wheels on one Windows agent via ODBC_TARGET_* overrides and validates wheel contents. |
| OneBranchPipelines/stages/build-macos-single-stage.yml | Adds optional dependency/install of consolidated mssql-python-odbc wheel before pytest. |
| OneBranchPipelines/stages/build-linux-single-stage.yml | Adds optional dependency/install of consolidated mssql-python-odbc wheel before pytest (via PIP_FIND_LINKS). |
| OneBranchPipelines/build-release-package-pipeline.yml | Always builds both packages; makes mssql-python stages depend on ConsolidateOdbc; swaps 7 ODBC stages for one build-all stage. |
| .gitignore | Stops ignoring mssql_python_odbc/libs/ now that it’s the committed source of truth. |
Suppressed comments (2)
tests/test_015_utf8_path_handling.py:1
- This test helper still falls back to
mssql_pythonwhenmssql_python_odbcis missing/incomplete, but the runtime resolver (GetOdbcLibsBaseDir) now requires the external package and throws if it’s missing or incomplete. The fallback makes the tests inconsistent with production behavior and can mask packaging issues. Recommendation (mandatory): remove the fallback and instead fail (or explicitlypytest.skip) whenmssql_python_odbcis not importable or does not contain a completelibs/payload for the platform.
setup_odbc.py:1 - When
ODBC_TARGET_PLATFORM_TAGis set,get_platform_info()returns an empty arch ifODBC_TARGET_ARCHis unset. That empty value is later used to build the targetlibs/globs (e.g.,libs/windows/{arch}), which can silently produce wrong/emptypackage_dataand yield broken wheels. Recommendation (mandatory): validate the override inputs—require a non-empty, expected arch forwin*and*linux*tags (and optionally validate allowed values per tag), and raise a clear error when the override is incomplete/invalid.
…); validate installed mssql-python-odbc wheel on Win/macOS pytest; repoint configure_dylibs to mssql_python_odbc/libs
…exact driver filenames - setup_odbc.get_platform_info now raises OSError if ODBC_TARGET_PLATFORM_TAG is set but ODBC_TARGET_ARCH is empty; an empty arch expanded the libs/ package_data globs to every architecture and could leak foreign-platform driver binaries into the wheel. - build-odbc-all-stage leak-check Must lists now assert the exact driver filenames (msodbcsql18.dll / libmsodbcsql.18.dylib / libmsodbcsql-18) instead of loose substrings that also matched support files. - test_015 prose updated to reference GetOdbcLibsBaseDir (the successor to the removed GetModuleDirectory).
Jahnvi Thakkar (jahnvi480)
requested review from
Gaurav Sharma (bewithgaurav) and
Sumit Sarabhai (sumitmsft)
July 31, 2026 10:29
…bundled-libs # Conflicts: # OneBranchPipelines/stages/build-odbc-linux-stage.yml # OneBranchPipelines/stages/wheel-installation-test-stage.yml
Gaurav Sharma (bewithgaurav)
previously requested changes
Aug 5, 2026
Gaurav Sharma (bewithgaurav)
left a comment
Collaborator
There was a problem hiding this comment.
need to add some failure case tests, and some improvement suggestions
Jahnvi Thakkar (jahnvi480)
added a commit
that referenced
this pull request
Aug 5, 2026
… description (#701) ### Work Item / Issue Reference <!-- IMPORTANT: Please follow the PR template guidelines below. For mssql-python maintainers: Insert your ADO Work Item ID below For external contributors: Insert Github Issue number below Only one reference is required - either GitHub issue OR ADO Work Item. --> <!-- mssql-python maintainers: ADO Work Item --> > [AB#46836](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/46836) ------------------------------------------------------------------- ### Summary Metadata-only re-release of the standalone **mssql-python-odbc** package (identical driver binaries as the already-published 18.6.2) that corrects the package license and ships a proper PyPI description. ## What changed - **License: MIT → Other/Proprietary License** — the `license` field and trove classifier in `setup_odbc.py`. The wheel redistributes Microsoft's proprietary ODBC Driver 18 for SQL Server (and the VC++ runtime it links against), so the previous `MIT` label was incorrect. The build script's own source stays MIT. - **PyPI `long_description`** — replaced the plain one-line description with a rendered Markdown description: project role, "do not install directly" guidance, docs link, and a **License information** section linking both redistributed Microsoft license texts (same style as `azure-cognitiveservices-speech`). `long_description_content_type` switched to `text/markdown`. - **Committed canonical `mssql_python_odbc/licenses/` dir** — byte-exact copies of the ODBC Driver and Visual Studio license texts. The description links and `license_files` now point here, so the links resolve on PyPI/GitHub and the wheel embeds the license texts deterministically — independent of the build-time-populated `libs/` tree (empty on `main`). - **Version 18.6.2 → 18.6.2.1** — packaging revision 1 of the same driver. A literal `v1` suffix is not a valid PEP 440 version and `+local` versions are rejected by PyPI's upload API, so the revision is expressed as a fourth numeric component. Ordering holds: `18.6.2 < 18.6.2.1 < 18.6.3`. - **Relaxed `mssql_python/pybind/CMakeLists.txt` version parser** — now accepts a trailing packaging-revision component / PEP 440 suffix. Only the leading MAJOR and MAJOR.MINOR are used to derive the driver filename, so the extra component has no effect on which driver is loaded. ## Ordering This lands **before** the Phase 2 wheel-slimming PR (#693). After this release publishes `18.6.2.1` to PyPI, a small follow-up bumps `mssql-python`'s pinned dependency from `==18.6.2` to `==18.6.2.1` so installs pull the correctly-licensed wheel. ## Validation - `black --check --line-length=100 setup_odbc.py` — passes. - CMake version-parse of `18.6.2.1` exercised with the real regex engine → `MAJOR=18`, `MAJOR_MINOR=18.6`, no `FATAL_ERROR`. - Driver binaries unchanged from 18.6.2 (metadata-only re-release).
…bundled-libs # Conflicts: # setup_odbc.py
…er negative-path tests
…macosx_15_0 The mssql-python wheel is tagged macosx_15_0_universal2 while the odbc wheel was tagged macosx_11_0_universal2, so the release pipeline never install-tested the 15_0 floor and the two wheels disagreed on the macOS floor. Bump the OneBranch macOS build/test agent to macos-latest (macOS-15, matching the pr-validation gate) and tag the odbc universal2 wheel macosx_15_0 (matrix + content-verification map) so both wheels share one floor and 15_0 is install-tested end to end.
Jahnvi Thakkar (jahnvi480)
requested a review
from Gaurav Sharma (bewithgaurav)
August 5, 2026 16:45
…opy is removed The multi-platform wheel-build stage deletes the committed mssql_python_odbc/ directory before running setup.py bdist_wheel (so pytest resolves the driver from the installed wheel, not the checkout). _read_odbc_version() read that now-deleted __init__.py and crashed the Windows x64 and macOS wheel builds with FileNotFoundError. Read the checkout copy when present (local dev, Windows arm64 where the dir is not removed), otherwise fall back to the already-installed mssql-python-odbc package metadata -- both derive from the same __version__, so it stays a single source of truth.
On musl libc (Alpine/musllinux) libstdc++ routes std::call_once through pthread_once, which cannot propagate an exception thrown by the callable and instead calls std::terminate() (SIGABRT). That turned the actionable 'install mssql-python-odbc' driver-load error into a hard crash on Alpine (test_025 aborted with returncode -6). Capture any failure from LoadDriverOrThrowException() as a std::exception_ptr inside the call_once callable and rethrow it from loadDriver() in a normal context, where pybind11 translates it into a Python exception. The stored error persists so every subsequent call re-raises the same actionable message.
The manylinux_2_28 aarch64 wheel build failed with 'sql.h: No such file or directory'. On Linux the ODBC Driver Manager headers come from the system unixODBC dev package in /usr/include, installed inside the build container via dnf/apk. On QEMU-emulated aarch64 that in-container install is unreliable (rpm/dnf can crash under emulation), leaving /usr/include/sql.h absent -- which only surfaced later as an opaque compile error. The ODBC headers are architecture- and libc-agnostic (pure API declarations), so install unixodbc-dev natively on the x86_64 host and docker cp the headers into the build container. This makes header provisioning deterministic regardless of container arch/libc. Also add a gate that fails loudly with the real cause if sql.h is still missing, instead of the downstream compile error.
Jahnvi Thakkar (jahnvi480)
dismissed
Gaurav Sharma (bewithgaurav)’s stale review
August 6, 2026 07:32
Please review it again
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Work Item / Issue Reference
Summary
This pull request makes significant updates to the build and packaging process for the
mssql-pythonandmssql-python-odbcPython packages, reflecting the transition to Phase 2 where the ODBC driver is fully separated into its own package. The pipeline now always builds both packages together, ensures the external ODBC wheel is installed before testing, and simplifies and streamlines build and test stages. Documentation and prompts are also updated to reflect these structural changes.Build and Pipeline Simplification:
mssql-pythonandmssql-python-odbcpackages together; parameters and logic for building only one package have been removed, simplifying configuration and execution. [1] [2]ODBC_BuildAllstage that produces all platform wheels from one host, replacing multiple per-platform ODBC build stages.mssql-pythonbuild stages now depend on theConsolidateOdbcstage and install the external ODBC wheel before running tests, validating the package split end-to-end during the build. The previous post-consolidation "TestBothWheels" stages are removed as redundant.Documentation and Prompt Updates:
mssql_python_odbc/libslocation for ODBC binaries and to clarify that these are now maintained in the standalonemssql-python-odbcpackage.Platform and Security Improvements:
macos-latest, macOS-15 Sequoia).mssql-pythonwheel, reflecting the unified build process.Release Pipeline Robustness:
mssql_python_odbc/__init__.py, ensuring the version pin is always in sync and eliminating the need for manual updates when the ODBC version changes.