Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8cee2e8
Add standalone mssql-python-odbc package for ODBC driver binaries
jahnvi480 Jul 7, 2026
69ca1b7
FIX: Robust ODBC driver fallback and address review feedback for pack…
jahnvi480 Jul 7, 2026
362b1df
FIX: Require complete odbc libs (driver + mssql-auth.dll) before usin…
jahnvi480 Jul 7, 2026
25ff315
Include vcredist (msvcp140.dll) in odbc package for full parity with …
jahnvi480 Jul 7, 2026
c3c3001
Address review: acquire GIL in GetOdbcLibsBaseDir, document driver-ve…
jahnvi480 Jul 7, 2026
d69ce14
Document deliberate vcredist/msvcp140.dll inclusion in odbc wheel
jahnvi480 Jul 7, 2026
40961b7
DOCS: Add CHANGELOG entry for the mssql-python-odbc package split
jahnvi480 Jul 8, 2026
74b6d62
Fix inaccurate setup_odbc.py docstring on libs packaging
jahnvi480 Jul 8, 2026
eb100db
Merge branch 'main' into jahnvi/odbc-package-split
jahnvi480 Jul 10, 2026
0884620
odbc: bump mssql-python-odbc package version 18.6.0 -> 18.6.2
jahnvi480 Jul 10, 2026
ab91887
Centralize ODBC driver version via single source of truth
jahnvi480 Jul 17, 2026
33a6084
Merge remote-tracking branch 'origin/main' into jahnvi/odbc-package-s…
jahnvi480 Jul 17, 2026
73733d4
Fix Linux pipeline uninstall parsing and ARM64 setup checks
jahnvi480 Jul 17, 2026
087b39b
Fix Linux pipeline version parsing quoting
jahnvi480 Jul 17, 2026
63a2117
Fix Linux pipeline host-side version parsing
jahnvi480 Jul 17, 2026
d9749ad
Fix Ubuntu Linux pipeline version parsing
jahnvi480 Jul 17, 2026
07888e9
Fix DRIVER_MAJOR_MINOR setup in all Linux uninstall steps
jahnvi480 Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ build/
**/build/
mssql_python.egg-info/

# ODBC package (mssql-python-odbc): transition-period build artifacts.
# During Phase 2, setup_odbc.py copies the current platform's driver binaries
# from mssql_python/libs into this tree so a wheel can be built locally. The
# release pipeline populates these per-platform; do not commit the copies.
mssql_python_odbc/libs/
mssql_python_odbc.egg-info/

# Python bytecode
__pycache__/
*.py[cod]
Expand Down
43 changes: 41 additions & 2 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1125,6 +1125,41 @@ std::string GetModuleDirectory() {
return parentDir.string();
}

// Resolve the base directory that contains the ODBC driver `libs/` tree.
//
// Post-split, the driver binaries ship in the standalone `mssql_python_odbc`
// package (a pure-data sibling with no native extension). We import it and use
// its directory as the base that `GetDriverPathCpp` (and the Windows
// `mssql-auth.dll` lookup) append `libs` to.
//
// During the Phase-2 transition we fall back to the bundled `mssql_python`
// directory when the external package is not installed, so a wheel that still
// bundles `libs/` keeps working. Importing `mssql_python_odbc` here is
// Alpine/musl-safe precisely because it is a separate pure package: it cannot
// trigger the partially-initialized-module circular import that motivated
// resolving these paths in C++ in the first place.
std::string GetOdbcLibsBaseDir() {
namespace fs = std::filesystem;
try {
py::object module = py::module::import("mssql_python_odbc");
py::object module_path = module.attr("__file__");
std::string module_file = module_path.cast<std::string>();

fs::path parentDir = fs::path(module_file).parent_path();
LOG("GetOdbcLibsBaseDir: Using external mssql_python_odbc package - directory='%s'",
parentDir.string().c_str());
return parentDir.string();
} catch (const py::error_already_set& e) {
// External package not installed. pybind11 has already fetched and
// cleared the CPython error indicator when constructing this
// exception, so it is safe to import `mssql_python` again below.
LOG("GetOdbcLibsBaseDir: mssql_python_odbc not available (%s); "
"falling back to bundled libs in mssql_python",
e.what());
return GetModuleDirectory();
}
Comment thread
jahnvi480 marked this conversation as resolved.
}

// Platform-agnostic function to load the driver dynamic library
DriverHandle LoadDriverLibrary(const std::string& driverPath) {
LOG("LoadDriverLibrary: Attempting to load ODBC driver from path='%s'", driverPath.c_str());
Expand Down Expand Up @@ -1240,8 +1275,12 @@ std::string GetDriverPathCpp(const std::string& moduleDir) {
DriverHandle LoadDriverOrThrowException() {
namespace fs = std::filesystem;

std::string moduleDir = GetModuleDirectory();
LOG("LoadDriverOrThrowException: Module directory resolved to '%s'", moduleDir.c_str());
// Resolve the base dir from the standalone `mssql_python_odbc` package
// (falls back to the bundled `mssql_python` libs during the transition).
// Both the driver path and the Windows `mssql-auth.dll` path below are
// derived from this directory.
std::string moduleDir = GetOdbcLibsBaseDir();
LOG("LoadDriverOrThrowException: ODBC libs base directory resolved to '%s'", moduleDir.c_str());

std::string archStr = ARCHITECTURE;
LOG("LoadDriverOrThrowException: Architecture detected as '%s'", archStr.c_str());
Expand Down
123 changes: 123 additions & 0 deletions mssql_python_odbc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.

mssql_python_odbc — Microsoft ODBC Driver 18 for SQL Server binaries.

Internal implementation package for ``mssql-python``. It ships the
platform-specific ODBC driver binaries (``msodbcsql18``) and their supporting
libraries so that ``mssql-python`` does not have to bundle them in its own
wheel. It is not meant for direct consumption — install ``mssql-python``
instead, which depends on this package.

The public surface is :func:`get_driver_path`, which returns the absolute path
to the ODBC driver shared library for the current platform/architecture. The
native ``mssql_python.ddbc_bindings`` extension resolves the same location in
C++ (see ``GetOdbcLibsBaseDir`` / ``GetDriverPathCpp``); this Python API mirrors
that logic for tooling, tests, and diagnostics.
"""

import os
import platform
import sys

__all__ = ["get_driver_path", "get_libs_dir", "__version__"]

# Version tracks the bundled Microsoft ODBC Driver 18 for SQL Server release.
__version__ = "18.6.0"

# Driver shared-library file names per platform (must match the names produced
# by the ODBC driver packaging and expected by the native loader).
_DRIVER_FILENAME = {
"windows": "msodbcsql18.dll",
"linux": "libmsodbcsql-18.6.so.2.1",
"macos": "libmsodbcsql.18.dylib",
}


def get_libs_dir() -> str:
"""Return the absolute path to this package's ``libs/`` directory.

This is the root under which the platform-specific ODBC binaries live
(``libs/<platform>/<arch>/...``). It is the base the native loader appends
``libs`` to when resolving the driver.
"""
Comment thread
jahnvi480 marked this conversation as resolved.
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "libs")


def _detect_arch() -> str:
"""Return the architecture directory name for the current interpreter.

Mirrors the compile-time detection in ``GetDriverPathCpp``:
* Windows uses ``x64`` / ``x86`` / ``arm64``.
* Linux and macOS use ``x86_64`` / ``arm64``.
"""
machine = platform.machine().lower()

if sys.platform.startswith("win"):
if machine in ("amd64", "x86_64"):
return "x64"
if machine in ("arm64", "aarch64"):
return "arm64"
if machine in ("x86", "i386", "i686"):
return "x86"
raise OSError(f"Unsupported Windows architecture: {platform.machine()!r}")

# Linux / macOS
if machine in ("x86_64", "amd64"):
return "x86_64"
if machine in ("arm64", "aarch64"):
return "arm64"
raise OSError(f"Unsupported architecture: {platform.machine()!r}")


def _detect_linux_distro_family() -> str:
"""Return the Linux distro family directory name.

Mirrors the ``/etc/*-release`` probing in ``GetDriverPathCpp`` so the Python
and C++ paths agree.
"""
if os.path.exists("/etc/alpine-release"):
return "alpine"
if os.path.exists("/etc/redhat-release") or os.path.exists("/etc/centos-release"):
return "rhel"
if os.path.exists("/etc/SuSE-release") or os.path.exists("/etc/SUSE-brand"):
return "suse"
return "debian_ubuntu" # default for Debian/Ubuntu and other glibc distros


def get_driver_path() -> str:
Comment thread
jahnvi480 marked this conversation as resolved.
Outdated
"""Return the absolute path to the ODBC driver shared library.

Resolves the platform/architecture (and, on Linux, the distro family) and
returns the full path to ``msodbcsql18`` inside this package.

Raises:
OSError: if the platform/architecture is unsupported.
FileNotFoundError: if the resolved driver file is not present (e.g. the
package was built without this platform's binaries).
"""
libs_dir = get_libs_dir()
arch = _detect_arch()

if sys.platform.startswith("win"):
driver_path = os.path.join(libs_dir, "windows", arch, _DRIVER_FILENAME["windows"])
elif sys.platform.startswith("darwin"):
driver_path = os.path.join(
libs_dir, "macos", arch, "lib", _DRIVER_FILENAME["macos"]
)
elif sys.platform.startswith("linux"):
distro = _detect_linux_distro_family()
driver_path = os.path.join(
libs_dir, "linux", distro, arch, "lib", _DRIVER_FILENAME["linux"]
)
else:
raise OSError(f"Unsupported platform: {sys.platform!r}")

if not os.path.isfile(driver_path):
raise FileNotFoundError(
f"ODBC driver not found at {driver_path!r}. The mssql-python-odbc "
f"package may not include binaries for this platform/architecture."
)

return driver_path
Loading
Loading