diff --git a/.github/workflows/refresh-build-dependencies.yml b/.github/workflows/refresh-build-dependencies.yml index ca56de461..45a360a6d 100644 --- a/.github/workflows/refresh-build-dependencies.yml +++ b/.github/workflows/refresh-build-dependencies.yml @@ -130,18 +130,30 @@ jobs: - name: Linux build artifact: linux lock: requirements-build-linux.txt - versions: "3.10 3.11 3.12 3.13 3.14" + versions: "3.10 3.11 3.12 3.13 3.14 3.15" platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl" - name: Linux test artifact: linux lock: requirements-test-linux.txt versions: "3.10 3.11 3.12 3.13 3.14" platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl" + - name: Linux test Python 3.15 + artifact: linux + lock: requirements-test-linux.txt + versions: "3.15" + platforms: "x86_64-manylinux_2_28 aarch64-manylinux_2_28 x86_64-unknown-linux-musl aarch64-unknown-linux-musl" + allowSourceBuilds: true - name: macOS artifact: macos lock: requirements-build-macos.txt versions: "3.10 3.11 3.12 3.13 3.14" platforms: "x86_64-apple-darwin aarch64-apple-darwin" + - name: macOS Python 3.15 + artifact: macos + lock: requirements-build-macos.txt + versions: "3.15" + platforms: "x86_64-apple-darwin aarch64-apple-darwin" + allowSourceBuilds: true # Windows ARM64 stages install build tools into the x64 host # interpreter before cross-compiling the target extension. - name: Windows build host @@ -149,6 +161,12 @@ jobs: lock: requirements-build-windows.txt versions: "3.10 3.11 3.12 3.13 3.14" platforms: "x86_64-pc-windows-msvc" + - name: Windows build host Python 3.15 + artifact: windows + lock: requirements-build-windows.txt + versions: "3.15" + platforms: "x86_64-pc-windows-msvc" + allowSourceBuilds: true - name: ODBC artifact: odbc lock: requirements-build-odbc.txt @@ -168,8 +186,11 @@ jobs: LOCK_FILE: generated-lock/${{ matrix.lock }} PLATFORMS: ${{ matrix.platforms }} PYTHON_VERSIONS: ${{ matrix.versions }} + ALLOW_SOURCE_BUILDS: ${{ matrix.allowSourceBuilds || false }} run: | set -euo pipefail + binary_args=(--only-binary :all:) + if [ "$ALLOW_SOURCE_BUILDS" = true ]; then binary_args=(); fi for platform in $PLATFORMS; do for version in $PYTHON_VERSIONS; do echo "Validating $LOCK_FILE for Python $version on $platform" @@ -179,7 +200,7 @@ jobs: --target "$RUNNER_TEMP/lock-validation" \ --python-version "$version" \ --python-platform "$platform" \ - --only-binary :all: \ + "${binary_args[@]}" \ --require-hashes \ --default-index https://pypi.org/simple \ -r "$LOCK_FILE" diff --git a/OneBranchPipelines/build-release-package-pipeline.yml b/OneBranchPipelines/build-release-package-pipeline.yml index b5b719cef..bf0904658 100644 --- a/OneBranchPipelines/build-release-package-pipeline.yml +++ b/OneBranchPipelines/build-release-package-pipeline.yml @@ -2,9 +2,9 @@ # OneBranch Release Pipeline for mssql-python # ========================================================================================= # Builds Python wheels for all supported platforms with SDL compliance: -# - Windows: Python 3.10-3.14 (x64 + ARM64) -# - macOS: Python 3.10-3.14 (Universal2 = x86_64 + ARM64 in single binary) -# - Linux: Python 3.10-3.14 on manylinux/musllinux (x86_64 + ARM64) +# - Windows: Python 3.10-3.15 (x64 + ARM64) +# - macOS: Python 3.10-3.15 (Universal2 = x86_64 + ARM64 in single binary) +# - Linux: Python 3.10-3.15 on manylinux/musllinux (x86_64 + ARM64) # # Security Features: # - ESRP code signing (Windows .pyd files only) @@ -82,19 +82,19 @@ parameters: # PLATFORM CONFIGURATIONS # ========================= # Each platform uses different matrix strategy: - # - Windows: Explicit per-version stages (9 stages for x64/ARM64 combos) - # - macOS: Explicit per-version stages (5 stages for universal2 builds) + # - Windows: Explicit per-version stages (11 stages for x64/ARM64 combos) + # - macOS: Explicit per-version stages (6 stages for universal2 builds) # - Linux: Per-distro stages, builds ALL Python versions in loop (4 stages) # Windows Configuration Matrix # Each entry creates separate stage: Win_py_ - # pyVer format: '310' = Python 3.10, '314' = Python 3.14 + # pyVer format: '310' = Python 3.10, '315' = Python 3.15 # arch: 'x64' (Intel/AMD 64-bit) or 'arm64' (ARM64, cross-compiled on x64) # Note: ARM64 builds use x64 host with ARM64 python.lib for cross-compilation - name: windowsConfigs type: object default: - # x64 builds (5 versions: 3.10-3.14) + # x64 builds (6 versions: 3.10-3.15) - pyVer: '310' arch: 'x64' - pyVer: '311' @@ -105,7 +105,9 @@ parameters: arch: 'x64' - pyVer: '314' arch: 'x64' - # ARM64 builds (4 versions: 3.11-3.14) + - pyVer: '315' + arch: 'x64' + # ARM64 builds (5 versions: 3.11-3.15) # 3.10 excluded due to limited ARM64 support - pyVer: '311' arch: 'arm64' @@ -115,23 +117,26 @@ parameters: arch: 'arm64' - pyVer: '314' arch: 'arm64' + - pyVer: '315' + arch: 'arm64' # macOS Configuration Matrix # Each entry creates separate stage: MacOS_py # All builds are Universal2 (x86_64 + ARM64 in single binary) - # pyVer format: '310' = Python 3.10, '314' = Python 3.14 + # pyVer format: '310' = Python 3.10, '315' = Python 3.15 - name: macosConfigs type: object default: - # 5 versions: 3.10-3.14 (all universal2) + # 6 versions: 3.10-3.15 (all universal2) - pyVer: '310' - pyVer: '311' - pyVer: '312' - pyVer: '313' - pyVer: '314' + - pyVer: '315' # Linux Configuration Matrix - # Each entry creates ONE stage that builds ALL Python versions (3.10-3.14) + # Each entry creates ONE stage that builds ALL Python versions (3.10-3.15) # tag: 'manylinux' (glibc-based, e.g., Ubuntu/CentOS) or 'musllinux' (musl-based, e.g., Alpine) # arch: CPU architecture for Docker platform # platform: Docker platform identifier for multi-arch builds @@ -375,17 +380,17 @@ extends: # ========================= # PIPELINE STAGES # ========================= - # mssql-python: 9 Windows + 5 macOS + 4 Linux + 1 Consolidate = 19 stages + # mssql-python: 11 Windows + 6 macOS + 4 Linux + 1 Consolidate = 22 stages # mssql-python-odbc: 1 ODBC_BuildAll (single host, all 7 wheels) + 1 ConsolidateOdbc = 2 stages - # Total: 21 stages, always built together. The mssql-python build stages depend on + # Total: 24 stages, always built together. The mssql-python build stages depend on # ConsolidateOdbc so they install the external ODBC wheel before pytest. stages: # ========================= # WINDOWS BUILD STAGES # ========================= # Strategy: Explicit stage per Python version × architecture - # Total: 9 stages (5 x64 + 4 ARM64) - # Python versions: 3.10-3.14 (x64), 3.11-3.14 (ARM64) + # Total: 11 stages (6 x64 + 5 ARM64) + # Python versions: 3.10-3.15 (x64), 3.11-3.15 (ARM64) # Each stage: # 1. Installs Python (UsePythonVersion or NuGet for 3.14) # 2. Downloads ARM64 python.lib if cross-compiling @@ -416,7 +421,7 @@ extends: # MACOS BUILD STAGES # ========================= # Strategy: Explicit stage per Python version - # Total: 5 stages (3.10-3.14) + # Total: 6 stages (3.10-3.15) # All builds are Universal2 (x86_64 + ARM64 in single .so binary) # Each stage: # 1. Installs Python via UsePythonVersion@0 @@ -446,7 +451,7 @@ extends: # ========================= # Strategy: One stage per distribution × architecture # Total: 4 stages (manylinux_2_28×2 + musllinux×2) - # Each stage builds ALL Python versions (3.10-3.14) in a loop + # Each stage builds ALL Python versions (3.10-3.15) in a loop # Distributions: # - manylinux_2_28: glibc 2.28+ (RHEL 8, Ubuntu 20.04+, etc.) — forward-compatible with newer glibc # - musllinux: musl-based (Alpine Linux) @@ -454,12 +459,12 @@ extends: # Each stage: # 1. Starts PyPA Docker container (manylinux_2_28 or musllinux_1_2) # 2. Starts SQL Server Docker container - # 3. For each Python version (cp310-cp314): + # 3. For each Python version (cp310-cp315): # a. Builds .so native extension # b. Builds wheel # c. Installs wheel in isolated directory # d. Runs pytest against SQL Server - # 4. Publishes artifacts (all 5 wheels) + # 4. Publishes artifacts (all 6 wheels) # 5. Component Governance + AntiMalware scanning - ${{ each config in parameters.linuxConfigs }}: - template: /OneBranchPipelines/stages/build-linux-single-stage.yml@self @@ -479,7 +484,7 @@ extends: # CONSOLIDATE STAGE # ========================= # Purpose: Collect all artifacts from platform builds into single dist/ folder - # Dependencies: All 18 build stages (9 Windows + 5 macOS + 4 Linux) + # Dependencies: All 21 build stages (11 Windows + 6 macOS + 4 Linux) # Stages run in parallel, Consolidate waits for ALL to complete # Outputs: # - dist/wheels/*.whl (all platform wheels) @@ -490,22 +495,25 @@ extends: - stage: Consolidate displayName: 'Consolidate All Artifacts' dependsOn: - # Windows dependencies (9 stages) + # Windows dependencies (11 stages) - Win_py310_x64 - Win_py311_x64 - Win_py312_x64 - Win_py313_x64 - Win_py314_x64 + - Win_py315_x64 - Win_py311_arm64 - Win_py312_arm64 - Win_py313_arm64 - Win_py314_arm64 - # macOS dependencies (5 stages) + - Win_py315_arm64 + # macOS dependencies (6 stages) - MacOS_py310 - MacOS_py311 - MacOS_py312 - MacOS_py313 - MacOS_py314 + - MacOS_py315 # Linux dependencies (4 stages) - Linux_manylinux_2_28_x86_64 - Linux_manylinux_2_28_aarch64 diff --git a/OneBranchPipelines/jobs/consolidate-artifacts-job.yml b/OneBranchPipelines/jobs/consolidate-artifacts-job.yml index 5ca4c5efe..d2b7b9ff1 100644 --- a/OneBranchPipelines/jobs/consolidate-artifacts-job.yml +++ b/OneBranchPipelines/jobs/consolidate-artifacts-job.yml @@ -1,7 +1,7 @@ # Consolidate Artifacts Job Template # Downloads artifacts from all platform build stages and consolidates into single dist/ folder -# Works with individual stage artifacts (18 stages total: 9 Windows + 5 macOS + 4 Linux) -# Each Linux stage builds 5 Python versions (3.10-3.14), resulting in 34 total wheels +# Works with individual stage artifacts (21 stages total: 11 Windows + 6 macOS + 4 Linux) +# Each Linux stage builds 6 Python versions (3.10-3.15), resulting in 41 total wheels parameters: - name: oneBranchType type: string @@ -75,17 +75,17 @@ jobs: echo "" WHEEL_COUNT=$(ls -1 $(ob_outputDirectory)/dist/*.whl 2>/dev/null | wc -l) echo "Total wheel count: $WHEEL_COUNT" - echo "Expected: 34 wheels (9 Windows + 5 macOS + 20 Linux)" + echo "Expected: 41 wheels (11 Windows + 6 macOS + 24 Linux)" # Hard-fail on a wheel-count mismatch (symmetric with the odbc consolidate job). # A mismatch means a build stage silently produced a partial set (e.g. a missing # Python version) or the build matrix changed without updating this count. - # Update the expected 34 whenever the Windows/macOS/Linux build matrix changes. - if [ "$WHEEL_COUNT" -ne 34 ]; then - echo "ERROR: Expected 34 wheels but found $WHEEL_COUNT" >&2 + # Update the expected 41 whenever the Windows/macOS/Linux build matrix changes. + if [ "$WHEEL_COUNT" -ne 41 ]; then + echo "ERROR: Expected 41 wheels but found $WHEEL_COUNT" >&2 exit 1 else - echo "SUCCESS: All 34 wheels consolidated!" + echo "SUCCESS: All 41 wheels consolidated!" fi displayName: 'Consolidate wheels from all platforms' diff --git a/OneBranchPipelines/stages/build-linux-single-stage.yml b/OneBranchPipelines/stages/build-linux-single-stage.yml index 62b500b5a..06565f34b 100644 --- a/OneBranchPipelines/stages/build-linux-single-stage.yml +++ b/OneBranchPipelines/stages/build-linux-single-stage.yml @@ -1,6 +1,6 @@ # Linux Single Configuration Stage Template # Builds Python wheels for a specific Linux distribution and architecture -# Builds for Python 3.10, 3.11, 3.12, 3.13, 3.14 within single job +# Builds for Python 3.10, 3.11, 3.12, 3.13, 3.14, 3.15 within single job # Tests each wheel after building with isolated pytest execution parameters: # Stage identifier (e.g., 'Linux_manylinux_2_28_x86_64') @@ -256,14 +256,14 @@ stages: ls -1 "$(Build.SourcesDirectory)/odbc_wheels"/*.whl displayName: 'Flatten ODBC wheels for pip find-links' - # Build wheels for all Python versions (3.10-3.14) and test each one + # Build wheels for all Python versions (3.10-3.15) and test each one - script: | set -euo pipefail if [[ "$(LINUX_TAG)" == "manylinux_2_28" ]]; then SHELL_EXE=bash; else SHELL_EXE=sh; fi docker exec build-$(LINUX_TAG)-$(ARCH) $SHELL_EXE -lc 'mkdir -p /workspace/dist' # Loop through all Python versions: build wheel -> test wheel -> repeat - for PYBIN in cp310 cp311 cp312 cp313 cp314; do + for PYBIN in cp310 cp311 cp312 cp313 cp314 cp315; do echo "" echo "=====================================================" echo "Building and testing $PYBIN on $(LINUX_TAG)/$(ARCH)" @@ -440,7 +440,7 @@ stages: echo "=====================================================" echo "✓ All Python versions built and tested successfully!" echo "=====================================================" - displayName: 'Build and test wheels for Python 3.10-3.14' + displayName: 'Build and test wheels for Python 3.10-3.15' env: DB_PASSWORD: $(DB_PASSWORD) diff --git a/OneBranchPipelines/stages/build-windows-single-stage.yml b/OneBranchPipelines/stages/build-windows-single-stage.yml index a81e045db..b1c8cdbb3 100644 --- a/OneBranchPipelines/stages/build-windows-single-stage.yml +++ b/OneBranchPipelines/stages/build-windows-single-stage.yml @@ -201,6 +201,7 @@ stages: "3.12" { "3.12.7" } "3.13" { "3.13.0" } "3.14" { "3.14.0" } + "3.15" { "3.15.0" } } $nugetUrl = "https://www.nuget.org/api/v2/package/pythonarm64/$nugetVersion" diff --git a/eng/pipelines/pr-validation-pipeline.yml b/eng/pipelines/pr-validation-pipeline.yml index 2107a5bfe..c19a3e8aa 100644 --- a/eng/pipelines/pr-validation-pipeline.yml +++ b/eng/pipelines/pr-validation-pipeline.yml @@ -66,6 +66,9 @@ jobs: LocalDB_Python314: sqlVersion: 'LocalDB' pythonVersion: '3.14' + LocalDB_Python315: + sqlVersion: 'LocalDB' + pythonVersion: '3.15' steps: - checkout: self @@ -74,6 +77,7 @@ jobs: inputs: versionSpec: '$(pythonVersion)' addToPath: true + allowUnstable: true githubToken: $(GITHUB_TOKEN) displayName: 'Use Python $(pythonVersion)' @@ -582,6 +586,10 @@ jobs: sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' sqlVersion: 'SQL2025' pythonVersion: '3.14' + SQL2025_Python315: + sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' + sqlVersion: 'SQL2025' + pythonVersion: '3.15' steps: - checkout: self @@ -591,6 +599,7 @@ jobs: inputs: versionSpec: '$(pythonVersion)' addToPath: true + allowUnstable: true displayName: 'Use Python $(pythonVersion) on macOS' - task: Cache@2 @@ -768,6 +777,11 @@ jobs: distroName: 'Debian-SQL2025' sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' useAzureSQL: 'false' + Debian_Python315: + dockerImage: 'python:3.15.0rc3-bookworm' + distroName: 'Debian-Python315' + sqlServerImage: 'mcr.microsoft.com/mssql/server:2025-latest' + useAzureSQL: 'false' steps: - checkout: self @@ -797,7 +811,17 @@ jobs: - script: | # Install dependencies in the container - if [ "$(distroName)" = "Ubuntu" ]; then + if [ "$(distroName)" = "Debian-Python315" ]; then + docker exec test-container-$(distroName) bash -c " + set -e + export DEBIAN_FRONTEND=noninteractive + export TZ=UTC + ln -snf /usr/share/zoneinfo/\$TZ /etc/localtime && echo \$TZ > /etc/timezone + apt-get update && + apt-get install -y cmake curl wget gnupg build-essential && + python3 --version + " + elif [ "$(distroName)" = "Ubuntu" ]; then docker exec test-container-$(distroName) bash -c " export DEBIAN_FRONTEND=noninteractive export TZ=UTC @@ -866,6 +890,7 @@ jobs: displayName: 'Install Python dependencies in $(distroName) container' - script: | + set -e # Build pybind bindings in the container PROFILER_BUILD=0 if [ "$(Build.Reason)" = "PullRequest" ] && @@ -1078,6 +1103,10 @@ jobs: dockerImage: 'python:3.11-bookworm' distroName: 'Debian' archName: 'arm64' + Debian_Python315_ARM64: + dockerImage: 'python:3.15.0rc3-bookworm' + distroName: 'Debian-Python315' + archName: 'arm64' steps: - script: | diff --git a/eng/requirements-build-macos.txt b/eng/requirements-build-macos.txt index 75745d44b..939ff74d1 100644 --- a/eng/requirements-build-macos.txt +++ b/eng/requirements-build-macos.txt @@ -1121,7 +1121,7 @@ psutil==7.2.2 \ --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 # via -r eng/../requirements.txt -pyarrow==25.0.1 \ +pyarrow==25.0.1 ; python_full_version < '3.15' \ --hash=sha256:0b1edbb2f385a6a65e9711b62ba86ac54a7816a3f8d17bb3e8a5929d65fb2485 \ --hash=sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b \ --hash=sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f \ @@ -1166,6 +1166,58 @@ pyarrow==25.0.1 \ --hash=sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44 \ --hash=sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f # via -r eng/../requirements.txt +pyarrow==26.0.0 ; python_full_version >= '3.15' \ + --hash=sha256:01c863a18bd9c8412453dd0d92de6d0ee7b2b3d6fb079d9734a4b2a3c8bd4453 \ + --hash=sha256:0cccd36e00ea3afeb52ded61f2721ce71f604853d70c45365c58324eb773d6ae \ + --hash=sha256:106bb9290fc6fd9a84138a9440038ef184bac86463543c5ff099229cb30d996c \ + --hash=sha256:13b0972a3dc71b642050d1bc72664a3916e14f59c943d8c1368154d6e4b0c2d5 \ + --hash=sha256:210cc9b83888b87cdc8f793eebb264f22b20d0dedbedefc73b9687a7047b4747 \ + --hash=sha256:240bd18a7487f8767616a948a69dd4e740a8bc36a1c9da49e4dc9a32c5c2faed \ + --hash=sha256:24f892fdf1ae1942d69d3f7742e2f49960ec95277cfb1a70b8a1d91f4a96d935 \ + --hash=sha256:290a74c48e9491b436fd5edacfadf357943f82aa45c81110bd83a69aab33d1cf \ + --hash=sha256:2b5fcd69c0e1107b79e55839877db5a6ed04651b73fd6fec581d09e230bed5e4 \ + --hash=sha256:2e4a413046eba9896e632925066c74095182200ba32e19ff0166bf64d2f936ac \ + --hash=sha256:3a4d235876f14b4136b4d616ec42eb469ea0d6ead336cae631aa1dd29b21c962 \ + --hash=sha256:3de30a7432b48b98b9decbd9e25a53bb9251d202c2e6c5a29a50869592ccb117 \ + --hash=sha256:41dd3661ef40790a78870052ad7a58ad827b27c67a4511f06962eb9e9b74d19b \ + --hash=sha256:4a5fa8dc70dd50808990ff36faf44088e357b353d86c7682dd92d4b78d4c97d5 \ + --hash=sha256:4bcba83299cb2b8f8e443d36c6ba6269a5034431879015fb0719495df8a14de2 \ + --hash=sha256:515a10dae2a1d236bc9c9209d0317acb6746ea63cd4f98704904af7156d90ed1 \ + --hash=sha256:5780d487ff6c6ed7b42298609680d87fe0036e529a9dc2e1105364bce9697f50 \ + --hash=sha256:5b827650e874f1f9f9392524ea3e9e3e8a245de5ba64acca1f81ab188090afb9 \ + --hash=sha256:5d5768d03426abe6526d5274adefa00abf00a7f81118c46e98b5a46390f5549e \ + --hash=sha256:645917e976671debabf854abab6e2b75c571ca4f82adc33a2d338697f7c27d93 \ + --hash=sha256:68cd662e9e2b00876a131950cf32336ace2d0865e1f9418763e3d3be8481dfa4 \ + --hash=sha256:6a628922ba20705fa964ca73e4ef959c2fb2f14b9bbec5589a6a1e68e6257c85 \ + --hash=sha256:6e89dee53aaeb50505ed6152ea55bc7ddfd4f4df264f5427ea255288d8f0e580 \ + --hash=sha256:6e949744dcfc2d379808f7013c5f9cafaf0f817656dff7d46c6931528dd1784b \ + --hash=sha256:734312d3d99088d9ec28c5b17bad40389bd8373a1afc10acb60b83fd217af087 \ + --hash=sha256:7aa12ab8e236789b1ecd2d6ecaef036b4e63d675ddf1864a43c6799d18f2d028 \ + --hash=sha256:7c3fda041e7078802589cf257750323ee3d0cd1e56e53a9b20ec845697fb3d28 \ + --hash=sha256:879331ddea2a26479fa18fade71e6facf684a6cf19f67daec3775c871569e8e5 \ + --hash=sha256:8e8e28c464552b5ca03e30d4504168c4425ce383884f8611b00e972f9fd933fc \ + --hash=sha256:90ddaf7c625307ad52f31a9b25c34fe5e4897c7529ee3481135822b2b6842ff1 \ + --hash=sha256:954d971b363b16ee41f89389a4053315dc71265f2ce5c2468eb0a910b1166268 \ + --hash=sha256:9db18a9dc0af52135c9eac549d80a7a882696efbe5406cf882b044525d4ecc2e \ + --hash=sha256:a0e4e92eeb088f1d7c2c04d6c7de8434c75abb4b4ccf0bbcd045aa7164c68d93 \ + --hash=sha256:a6ca849f90cf73fe361f08a5762c783ead9671e4548c1f558cc637b54c9103f2 \ + --hash=sha256:ab6914db225d7f399652ae1f08588dfbc9efe617612715701e3d9d5cfa5ca19f \ + --hash=sha256:c2ba350957076b1b3a22f549261dc3e9c67ca20816d8bd5f79d7b9c69be4c4c2 \ + --hash=sha256:ca77c43ca55bfc9a4eeb1f0cd5f093f08731b77c24cdba0829035f084959b0bb \ + --hash=sha256:cc903e1069e9dd5e9dcf780324c0112e27e051e422ecfaff574fb33ed65d9160 \ + --hash=sha256:ce28748cbeb0f29c3ce9603782979c7117580fc76f16aa3ca448b38a22281adb \ + --hash=sha256:d58798c4d8d629700058e9afc1e16b9801023f3ce4dc1c92d945e79b5ffe4e98 \ + --hash=sha256:e2a1856e9565fe2679863b372478c681806aebbf7d0a6e72f33e77f804e647d6 \ + --hash=sha256:e3b190ba1d3d22a5a8758597f797111b77d433473744352a184a5ee0a42d672e \ + --hash=sha256:e890816e5ee89c74a0f8b9379fe8b5ba83f46132b2a0bbb9b1c21359ec30dfda \ + --hash=sha256:eaf9e7cc7ab59f6c760232bbde18f64d559bbc50544841303bfb32be53533297 \ + --hash=sha256:ee341973f78a0b46e073d065e88e75026a9c584051e97f98a0d05d96c6bac7dd \ + --hash=sha256:f1c1b4263fd13abbc339a16f2bf19f3a5cbf2a620853d812b1256f03c5342cb8 \ + --hash=sha256:f7444ea6975c49a857c68f9bd8fa11acae96dede63d120ffb3bf0a603ea82516 \ + --hash=sha256:f800e9e722c145ccd18012d82a864cb21bfee4ba4ceffde77100d25eced511a9 \ + --hash=sha256:fcdd1e04982637c6042337d3e24d472f938f01fdc502e2b994844b726d12c3f4 \ + --hash=sha256:ff1e816af7abff71f289242e109217036723ce36aca74ad6691e52d964a74afa + # via -r eng/../requirements.txt pybind11==3.1.0 \ --hash=sha256:a1cc06b524ab3edca51f8ad3895f9c4fa20b8b19283173dff4ae781449dc9639 \ --hash=sha256:b8488090f8acffbcb6b5d6a85571a6827a0a2981ffb75e5a0b27b87c4a6b7dd0 diff --git a/eng/requirements-test-linux.txt b/eng/requirements-test-linux.txt index a2fdeea14..5f7a6019d 100644 --- a/eng/requirements-test-linux.txt +++ b/eng/requirements-test-linux.txt @@ -1105,7 +1105,7 @@ psutil==7.2.2 \ --hash=sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00 \ --hash=sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8 # via -r eng/../requirements.txt -pyarrow==25.0.1 \ +pyarrow==25.0.1 ; python_full_version < '3.15' \ --hash=sha256:0b1edbb2f385a6a65e9711b62ba86ac54a7816a3f8d17bb3e8a5929d65fb2485 \ --hash=sha256:0b726ad7e7b669be982b0c71c07fe4b037d654354130da79a7902a669e93a66b \ --hash=sha256:0befcf816e45a1af33ac775a9970b749e4868a230c7372f0ae5e932bee27039f \ @@ -1150,6 +1150,58 @@ pyarrow==25.0.1 \ --hash=sha256:f3831aaa25c67a99f99dc8b05873cb9d64560390372e2aa197ce9dd4a3f06a44 \ --hash=sha256:f729cfdbd36fd99d543b67a914d2de044c84ebe45be8b34902b299b608c15c8f # via -r eng/../requirements.txt +pyarrow==26.0.0 ; python_full_version >= '3.15' \ + --hash=sha256:01c863a18bd9c8412453dd0d92de6d0ee7b2b3d6fb079d9734a4b2a3c8bd4453 \ + --hash=sha256:0cccd36e00ea3afeb52ded61f2721ce71f604853d70c45365c58324eb773d6ae \ + --hash=sha256:106bb9290fc6fd9a84138a9440038ef184bac86463543c5ff099229cb30d996c \ + --hash=sha256:13b0972a3dc71b642050d1bc72664a3916e14f59c943d8c1368154d6e4b0c2d5 \ + --hash=sha256:210cc9b83888b87cdc8f793eebb264f22b20d0dedbedefc73b9687a7047b4747 \ + --hash=sha256:240bd18a7487f8767616a948a69dd4e740a8bc36a1c9da49e4dc9a32c5c2faed \ + --hash=sha256:24f892fdf1ae1942d69d3f7742e2f49960ec95277cfb1a70b8a1d91f4a96d935 \ + --hash=sha256:290a74c48e9491b436fd5edacfadf357943f82aa45c81110bd83a69aab33d1cf \ + --hash=sha256:2b5fcd69c0e1107b79e55839877db5a6ed04651b73fd6fec581d09e230bed5e4 \ + --hash=sha256:2e4a413046eba9896e632925066c74095182200ba32e19ff0166bf64d2f936ac \ + --hash=sha256:3a4d235876f14b4136b4d616ec42eb469ea0d6ead336cae631aa1dd29b21c962 \ + --hash=sha256:3de30a7432b48b98b9decbd9e25a53bb9251d202c2e6c5a29a50869592ccb117 \ + --hash=sha256:41dd3661ef40790a78870052ad7a58ad827b27c67a4511f06962eb9e9b74d19b \ + --hash=sha256:4a5fa8dc70dd50808990ff36faf44088e357b353d86c7682dd92d4b78d4c97d5 \ + --hash=sha256:4bcba83299cb2b8f8e443d36c6ba6269a5034431879015fb0719495df8a14de2 \ + --hash=sha256:515a10dae2a1d236bc9c9209d0317acb6746ea63cd4f98704904af7156d90ed1 \ + --hash=sha256:5780d487ff6c6ed7b42298609680d87fe0036e529a9dc2e1105364bce9697f50 \ + --hash=sha256:5b827650e874f1f9f9392524ea3e9e3e8a245de5ba64acca1f81ab188090afb9 \ + --hash=sha256:5d5768d03426abe6526d5274adefa00abf00a7f81118c46e98b5a46390f5549e \ + --hash=sha256:645917e976671debabf854abab6e2b75c571ca4f82adc33a2d338697f7c27d93 \ + --hash=sha256:68cd662e9e2b00876a131950cf32336ace2d0865e1f9418763e3d3be8481dfa4 \ + --hash=sha256:6a628922ba20705fa964ca73e4ef959c2fb2f14b9bbec5589a6a1e68e6257c85 \ + --hash=sha256:6e89dee53aaeb50505ed6152ea55bc7ddfd4f4df264f5427ea255288d8f0e580 \ + --hash=sha256:6e949744dcfc2d379808f7013c5f9cafaf0f817656dff7d46c6931528dd1784b \ + --hash=sha256:734312d3d99088d9ec28c5b17bad40389bd8373a1afc10acb60b83fd217af087 \ + --hash=sha256:7aa12ab8e236789b1ecd2d6ecaef036b4e63d675ddf1864a43c6799d18f2d028 \ + --hash=sha256:7c3fda041e7078802589cf257750323ee3d0cd1e56e53a9b20ec845697fb3d28 \ + --hash=sha256:879331ddea2a26479fa18fade71e6facf684a6cf19f67daec3775c871569e8e5 \ + --hash=sha256:8e8e28c464552b5ca03e30d4504168c4425ce383884f8611b00e972f9fd933fc \ + --hash=sha256:90ddaf7c625307ad52f31a9b25c34fe5e4897c7529ee3481135822b2b6842ff1 \ + --hash=sha256:954d971b363b16ee41f89389a4053315dc71265f2ce5c2468eb0a910b1166268 \ + --hash=sha256:9db18a9dc0af52135c9eac549d80a7a882696efbe5406cf882b044525d4ecc2e \ + --hash=sha256:a0e4e92eeb088f1d7c2c04d6c7de8434c75abb4b4ccf0bbcd045aa7164c68d93 \ + --hash=sha256:a6ca849f90cf73fe361f08a5762c783ead9671e4548c1f558cc637b54c9103f2 \ + --hash=sha256:ab6914db225d7f399652ae1f08588dfbc9efe617612715701e3d9d5cfa5ca19f \ + --hash=sha256:c2ba350957076b1b3a22f549261dc3e9c67ca20816d8bd5f79d7b9c69be4c4c2 \ + --hash=sha256:ca77c43ca55bfc9a4eeb1f0cd5f093f08731b77c24cdba0829035f084959b0bb \ + --hash=sha256:cc903e1069e9dd5e9dcf780324c0112e27e051e422ecfaff574fb33ed65d9160 \ + --hash=sha256:ce28748cbeb0f29c3ce9603782979c7117580fc76f16aa3ca448b38a22281adb \ + --hash=sha256:d58798c4d8d629700058e9afc1e16b9801023f3ce4dc1c92d945e79b5ffe4e98 \ + --hash=sha256:e2a1856e9565fe2679863b372478c681806aebbf7d0a6e72f33e77f804e647d6 \ + --hash=sha256:e3b190ba1d3d22a5a8758597f797111b77d433473744352a184a5ee0a42d672e \ + --hash=sha256:e890816e5ee89c74a0f8b9379fe8b5ba83f46132b2a0bbb9b1c21359ec30dfda \ + --hash=sha256:eaf9e7cc7ab59f6c760232bbde18f64d559bbc50544841303bfb32be53533297 \ + --hash=sha256:ee341973f78a0b46e073d065e88e75026a9c584051e97f98a0d05d96c6bac7dd \ + --hash=sha256:f1c1b4263fd13abbc339a16f2bf19f3a5cbf2a620853d812b1256f03c5342cb8 \ + --hash=sha256:f7444ea6975c49a857c68f9bd8fa11acae96dede63d120ffb3bf0a603ea82516 \ + --hash=sha256:f800e9e722c145ccd18012d82a864cb21bfee4ba4ceffde77100d25eced511a9 \ + --hash=sha256:fcdd1e04982637c6042337d3e24d472f938f01fdc502e2b994844b726d12c3f4 \ + --hash=sha256:ff1e816af7abff71f289242e109217036723ce36aca74ad6691e52d964a74afa + # via -r eng/../requirements.txt pybind11==3.1.0 \ --hash=sha256:a1cc06b524ab3edca51f8ad3895f9c4fa20b8b19283173dff4ae781449dc9639 \ --hash=sha256:b8488090f8acffbcb6b5d6a85571a6827a0a2981ffb75e5a0b27b87c4a6b7dd0 diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 79436ab2e..d4b85e56b 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -2746,22 +2746,15 @@ def executemany( # pylint: disable=too-many-locals,too-many-branches,too-many-s f"Failed to convert parameter to Decimal at row " f"{row_index}, column {i} (value type: {type(val).__name__})" ) - # Split str(val) from the decimal parse so we only chain a - # cause we know is value-free. decimal.DecimalException - # messages (e.g. ConversionSyntax) never echo the input, so - # they are safe to preserve for debugging. str(val) itself - # or any other error could carry the value in its message - # and surface through __cause__ / formatted tracebacks, so - # those are re-raised with the chain suppressed (from None). + # Raise outside the handler so conversion exceptions are + # not retained as context with rejected input. + conversion_failed = False try: val_text = str(val) - except Exception: # pylint: disable=broad-exception-caught - raise ValueError(err_msg) from None - try: processed_row[i] = format(decimal.Decimal(val_text), "f") - except decimal.DecimalException as e: - raise ValueError(err_msg) from e except Exception: # pylint: disable=broad-exception-caught + conversion_failed = True + if conversion_failed: raise ValueError(err_msg) from None processed_parameters.append(processed_row) diff --git a/mssql_python/pybind/CMakeLists.txt b/mssql_python/pybind/CMakeLists.txt index 77d599bd5..069a1b165 100644 --- a/mssql_python/pybind/CMakeLists.txt +++ b/mssql_python/pybind/CMakeLists.txt @@ -387,6 +387,12 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang endif() endif() +# Python 3.15 defines newer POSIX feature levels than glibc's C++ headers. +# Process Python.h first so libc sees the matching feature-test macros. +if(UNIX) + target_compile_options(ddbc_bindings PRIVATE -include Python.h) +endif() + # Add macOS-specific string conversion fix if(APPLE) message(STATUS "Enabling macOS string conversion fix") diff --git a/mssql_python/pybind/ddbc_bindings.h b/mssql_python/pybind/ddbc_bindings.h index 32d9f8067..748ecc5ca 100644 --- a/mssql_python/pybind/ddbc_bindings.h +++ b/mssql_python/pybind/ddbc_bindings.h @@ -3,16 +3,19 @@ #pragma once -// pybind11.h must be the first include -#include -#include -#include +// Python.h must precede standard-library headers so its feature-test macros +// are established before libc headers consume them. +#include #include #include #include #include #include // Add this line for datetime support #include + +#include +#include +#include #include #include diff --git a/requirements.txt b/requirements.txt index daffd1a1c..2b18cd919 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,8 @@ zstandard coverage unittest-xml-reporting psutil -pyarrow +pyarrow==25.0.1; python_version < "3.15" +pyarrow==26.0.0; python_version >= "3.15" polars # Runtime dependencies needed for tests diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index dbd917879..001440edf 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -10412,6 +10412,8 @@ def test_setinputsizes_sql_decimal_unconvertible_value(db_connection): # ...and the sensitive value / raw row is NOT leaked into the message. assert sensitive_value not in message assert repr((sensitive_value,)) not in message # no repr of the parameter tuple + assert exc_info.value.__cause__ is None + assert exc_info.value.__context__ is None # ...nor into the chained cause or the fully formatted traceback, which # is what tracebacks and APM/log shippers actually capture. formatted = "".join( @@ -10457,6 +10459,7 @@ def __str__(self): # exposes it. assert secret not in str(exc_info.value) assert exc_info.value.__cause__ is None + assert exc_info.value.__context__ is None formatted = "".join( traceback.format_exception( type(exc_info.value), exc_info.value, exc_info.value.__traceback__ @@ -10501,6 +10504,7 @@ def test_setinputsizes_sql_decimal_non_decimal_exception_no_leak(db_connection): assert "column 0" in message # ...no chained cause (the non-DecimalException branch suppresses it)... assert exc_info.value.__cause__ is None + assert exc_info.value.__context__ is None # ...and the input is absent from the message and formatted traceback. assert sensitive_value not in message formatted = "".join( diff --git a/tests/test_009_pooling.py b/tests/test_009_pooling.py index c8cafed8f..28b7465bc 100644 --- a/tests/test_009_pooling.py +++ b/tests/test_009_pooling.py @@ -1351,7 +1351,6 @@ def collect_children(): collect_barrier.wait() assert free_entered.wait(10), "Cursor finalizer did not enter free" assert not errors, errors - assert cursor_ref() is None, "GC did not clear the cursor weakref" assert not connection._cursors, "Connection.close would still see the cursor" if not explicit_close: @@ -1369,6 +1368,15 @@ def collect_children(): native = None collect_barrier.wait() + remaining_cursor = cursor_ref() + if remaining_cursor is not None: + # Python 3.15 may finalize a cyclic object before reclaiming + # its self-cycle. Break only the synthetic test cycle, then + # verify that no production reference keeps the cursor alive. + remaining_cursor.cycle = None + del remaining_cursor + gc.collect() + assert cursor_ref() is None, "GC did not clear the cursor weakref" assert finalizer_statement.calls == 1 assert finalizer_statement.completed, errors assert not errors, errors