From 3d4604d70ca9f944dcbf6df3f1e001897b2ba085 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Tue, 24 Feb 2026 15:00:18 -0500 Subject: [PATCH 1/2] feat: add support for Python 3.14 --- .github/workflows/build.yaml | 16 ++++++------ .github/workflows/pr.yaml | 26 +++++++++---------- .github/workflows/test.yaml | 6 ++--- .../trigger-breaking-change-alert.yaml | 2 +- .../all_cuda-129_arch-aarch64.yaml | 2 +- .../all_cuda-129_arch-x86_64.yaml | 2 +- .../all_cuda-131_arch-aarch64.yaml | 2 +- .../all_cuda-131_arch-x86_64.yaml | 2 +- conda/recipes/kvikio/recipe.yaml | 2 +- cpp/doxygen/main_page.md | 4 +-- dependencies.yaml | 6 ++++- docs/source/install.rst | 8 +++--- python/kvikio/pyproject.toml | 1 + python/libkvikio/pyproject.toml | 1 + 14 files changed, 43 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 759b71f94e..3b610e849b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -35,7 +35,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -46,7 +46,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -58,7 +58,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -68,7 +68,7 @@ jobs: if: github.ref_type == 'branch' needs: python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.14 with: arch: "amd64" branch: ${{ inputs.branch }} @@ -80,7 +80,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-cpp: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.14 with: matrix_filter: group_by([.ARCH, (.CUDA_VER|split(".")|map(tonumber)|.[0])]) | map(max_by(.PY_VER|split(".")|map(tonumber))) build_type: ${{ inputs.build_type || 'branch' }} @@ -94,7 +94,7 @@ jobs: wheel-build-python: needs: wheel-build-cpp secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -109,7 +109,7 @@ jobs: wheel-publish-cpp: needs: wheel-build-cpp secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -120,7 +120,7 @@ jobs: wheel-publish-python: needs: wheel-build-python secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@python-3.14 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2a4c3c5694..a64b9cc5a8 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -27,7 +27,7 @@ jobs: - wheel-python-tests - telemetry-setup secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@python-3.14 if: always() with: needs: ${{ toJSON(needs) }} @@ -49,7 +49,7 @@ jobs: target-branch: ${{ fromJSON(steps.get-pr-info.outputs.pr-info).base.ref }} changed-files: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@python-3.14 with: files_yaml: | build_docs: @@ -167,13 +167,13 @@ jobs: checks: secrets: inherit needs: telemetry-setup - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@python-3.14 with: ignored_pr_jobs: telemetry-summarize conda-cpp-build: needs: checks secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@python-3.14 with: build_type: pull-request node_type: cpu8 @@ -181,7 +181,7 @@ jobs: conda-cpp-tests: needs: [conda-cpp-build, changed-files] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@python-3.14 if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp with: build_type: pull-request @@ -189,7 +189,7 @@ jobs: conda-java-tests: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.14 with: build_type: pull-request node_type: "gpu-l4-latest-1" @@ -199,7 +199,7 @@ jobs: conda-python-build: needs: conda-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@python-3.14 with: build_type: pull-request script: ci/build_python.sh @@ -208,7 +208,7 @@ jobs: conda-python-tests: needs: [conda-python-build, changed-files] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@python-3.14 if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_conda with: build_type: pull-request @@ -216,7 +216,7 @@ jobs: docs-build: needs: [conda-python-build, changed-files] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.14 if: fromJSON(needs.changed-files.outputs.changed_file_groups).build_docs with: build_type: pull-request @@ -227,7 +227,7 @@ jobs: devcontainer: needs: telemetry-setup secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/build-in-devcontainer.yaml@python-3.14 with: arch: '["amd64", "arm64"]' cuda: '["13.1"]' @@ -243,7 +243,7 @@ jobs: sccache --show-adv-stats | tee telemetry-artifacts/sccache-stats.txt; wheel-cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.14 with: matrix_filter: group_by([.ARCH, (.CUDA_VER|split(".")|map(tonumber)|.[0])]) | map(max_by(.PY_VER|split(".")|map(tonumber))) build_type: pull-request @@ -254,7 +254,7 @@ jobs: wheel-python-build: needs: wheel-cpp-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@python-3.14 with: build_type: pull-request node_type: cpu8 @@ -266,7 +266,7 @@ jobs: wheel-python-tests: needs: [wheel-python-build, changed-files] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@python-3.14 if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_wheels with: build_type: pull-request diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6365cc65bb..3ae6202923 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -25,7 +25,7 @@ on: jobs: cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@python-3.14 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} @@ -34,7 +34,7 @@ jobs: sha: ${{ inputs.sha }} python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@python-3.14 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} @@ -43,7 +43,7 @@ jobs: sha: ${{ inputs.sha }} conda-java-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@python-3.14 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/trigger-breaking-change-alert.yaml b/.github/workflows/trigger-breaking-change-alert.yaml index c471e2a151..680142fe32 100644 --- a/.github/workflows/trigger-breaking-change-alert.yaml +++ b/.github/workflows/trigger-breaking-change-alert.yaml @@ -12,7 +12,7 @@ jobs: trigger-notifier: if: contains(github.event.pull_request.labels.*.name, 'breaking') secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@python-3.14 with: sender_login: ${{ github.event.sender.login }} sender_avatar: ${{ github.event.sender.avatar_url }} diff --git a/conda/environments/all_cuda-129_arch-aarch64.yaml b/conda/environments/all_cuda-129_arch-aarch64.yaml index d13f417ccd..4dc2570330 100644 --- a/conda/environments/all_cuda-129_arch-aarch64.yaml +++ b/conda/environments/all_cuda-129_arch-aarch64.yaml @@ -29,7 +29,7 @@ dependencies: - pytest-cov - pytest-timeout - pytest<9.0.0 -- python>=3.11,<3.14 +- python>=3.11 - rangehttpserver - rapids-build-backend>=0.4.0,<0.5.0 - rapids-dask-dependency==26.4.*,>=0.0.0a0 diff --git a/conda/environments/all_cuda-129_arch-x86_64.yaml b/conda/environments/all_cuda-129_arch-x86_64.yaml index c7a28d9d3c..0e28a7bc9a 100644 --- a/conda/environments/all_cuda-129_arch-x86_64.yaml +++ b/conda/environments/all_cuda-129_arch-x86_64.yaml @@ -29,7 +29,7 @@ dependencies: - pytest-cov - pytest-timeout - pytest<9.0.0 -- python>=3.11,<3.14 +- python>=3.11 - rangehttpserver - rapids-build-backend>=0.4.0,<0.5.0 - rapids-dask-dependency==26.4.*,>=0.0.0a0 diff --git a/conda/environments/all_cuda-131_arch-aarch64.yaml b/conda/environments/all_cuda-131_arch-aarch64.yaml index bfea5d2afb..35d17ce5af 100644 --- a/conda/environments/all_cuda-131_arch-aarch64.yaml +++ b/conda/environments/all_cuda-131_arch-aarch64.yaml @@ -29,7 +29,7 @@ dependencies: - pytest-cov - pytest-timeout - pytest<9.0.0 -- python>=3.11,<3.14 +- python>=3.11 - rangehttpserver - rapids-build-backend>=0.4.0,<0.5.0 - rapids-dask-dependency==26.4.*,>=0.0.0a0 diff --git a/conda/environments/all_cuda-131_arch-x86_64.yaml b/conda/environments/all_cuda-131_arch-x86_64.yaml index 4addf115dc..f18fc4ba4b 100644 --- a/conda/environments/all_cuda-131_arch-x86_64.yaml +++ b/conda/environments/all_cuda-131_arch-x86_64.yaml @@ -29,7 +29,7 @@ dependencies: - pytest-cov - pytest-timeout - pytest<9.0.0 -- python>=3.11,<3.14 +- python>=3.11 - rangehttpserver - rapids-build-backend>=0.4.0,<0.5.0 - rapids-dask-dependency==26.4.*,>=0.0.0a0 diff --git a/conda/recipes/kvikio/recipe.yaml b/conda/recipes/kvikio/recipe.yaml index 3d8295764d..11ca6e784e 100644 --- a/conda/recipes/kvikio/recipe.yaml +++ b/conda/recipes/kvikio/recipe.yaml @@ -10,7 +10,7 @@ context: head_rev: '${{ git.head_rev(".")[:8] }}' py_abi_min: ${{ env.get("RAPIDS_PY_VERSION") }} py_buildstring : ${{ py_abi_min | version_to_buildstring }} - py_runtime_latest: "3.13" + py_runtime_latest: "3.14" package: name: kvikio diff --git a/cpp/doxygen/main_page.md b/cpp/doxygen/main_page.md index 1d01b10629..0114097217 100644 --- a/cpp/doxygen/main_page.md +++ b/cpp/doxygen/main_page.md @@ -49,10 +49,10 @@ Install the **nightly release** from the ``rapidsai-nightly`` channel with the f mamba install -c rapidsai-nightly -c conda-forge libkvikio # Create new environment (CUDA 13) -mamba create -n libkvikio-env -c rapidsai-nightly -c conda-forge python=3.13 cuda-version=13.1 libkvikio +mamba create -n libkvikio-env -c rapidsai-nightly -c conda-forge python=3.14 cuda-version=13.1 libkvikio # Create new environment (CUDA 12) -mamba create -n libkvikio-env -c rapidsai-nightly -c conda-forge python=3.13 cuda-version=12.9 libkvikio +mamba create -n libkvikio-env -c rapidsai-nightly -c conda-forge python=3.14 cuda-version=12.9 libkvikio ``` --- diff --git a/dependencies.yaml b/dependencies.yaml index fa0cfd8016..1fe93b10ad 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -281,8 +281,12 @@ dependencies: packages: - python=3.13 - matrix: + py: "3.14" packages: - - python>=3.11,<3.14 + - python=3.14 + - matrix: + packages: + - python>=3.11 rapids_build_skbuild: common: - output_types: [conda, requirements, pyproject] diff --git a/docs/source/install.rst b/docs/source/install.rst index e345755c14..4eeb7c5c32 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -17,10 +17,10 @@ Install the **stable release** from the ``rapidsai`` channel like: mamba install -c rapidsai -c conda-forge kvikio # Create new environment (CUDA 13) - mamba create -n kvikio-env -c rapidsai -c conda-forge python=3.13 cuda-version=13.1 kvikio + mamba create -n kvikio-env -c rapidsai -c conda-forge python=3.14 cuda-version=13.1 kvikio # Create new environment (CUDA 12) - mamba create -n kvikio-env -c rapidsai -c conda-forge python=3.13 cuda-version=12.9 kvikio + mamba create -n kvikio-env -c rapidsai -c conda-forge python=3.14 cuda-version=12.9 kvikio Install the **nightly release** from the ``rapidsai-nightly`` channel like: @@ -30,10 +30,10 @@ Install the **nightly release** from the ``rapidsai-nightly`` channel like: mamba install -c rapidsai-nightly -c conda-forge kvikio # Create new environment (CUDA 13) - mamba create -n kvikio-env -c rapidsai-nightly -c conda-forge python=3.13 cuda-version=13.1 kvikio + mamba create -n kvikio-env -c rapidsai-nightly -c conda-forge python=3.14 cuda-version=13.1 kvikio # Create new environment (CUDA 12) - mamba create -n kvikio-env -c rapidsai-nightly -c conda-forge python=3.13 cuda-version=12.9 kvikio + mamba create -n kvikio-env -c rapidsai-nightly -c conda-forge python=3.14 cuda-version=12.9 kvikio .. note:: diff --git a/python/kvikio/pyproject.toml b/python/kvikio/pyproject.toml index 9133e0eec7..a8d391a858 100644 --- a/python/kvikio/pyproject.toml +++ b/python/kvikio/pyproject.toml @@ -32,6 +32,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.optional-dependencies] diff --git a/python/libkvikio/pyproject.toml b/python/libkvikio/pyproject.toml index b3332c8a8c..5e80cf0455 100644 --- a/python/libkvikio/pyproject.toml +++ b/python/libkvikio/pyproject.toml @@ -25,6 +25,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.entry-points."cmake.prefix"] From 4d9bf2ab2396fd320b982856dc68a6320e6d4155 Mon Sep 17 00:00:00 2001 From: Gil Forsyth Date: Tue, 24 Feb 2026 15:42:48 -0500 Subject: [PATCH 2/2] fix(tests): lift closure to module level Python 3.14 `multiprocessing` uses `forkserver` by default instead of `fork`, so the target function should be serializable using `pickle` --- python/kvikio/tests/test_hdfs_io.py | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/python/kvikio/tests/test_hdfs_io.py b/python/kvikio/tests/test_hdfs_io.py index 709bb04992..89ab3835fa 100644 --- a/python/kvikio/tests/test_hdfs_io.py +++ b/python/kvikio/tests/test_hdfs_io.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. All rights reserved. # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations @@ -177,38 +177,40 @@ def test_parallel_read_partial( assert np.array_equal(result_buf_np, expected_buf) +def run_bad_server(queue: Queue[int]) -> None: + """Run a bad WebHDFS server that returns JSON missing the 'length' field""" + + class BadHandler(BaseHTTPRequestHandler): + def do_GET(self) -> None: + parsed = urllib.parse.urlparse(self.path) + query = urllib.parse.parse_qs(parsed.query) + + if query.get("op") == ["GETFILESTATUS"]: + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.end_headers() + # Missing "length" field + response = json.dumps({}) + self.wfile.write(response.encode()) + else: + self.send_response(400) + self.end_headers() + + def log_message(self, format: str, *args: Any) -> None: + pass + + port = utils.find_free_port() + server = HTTPServer((utils.localhost(), port), BadHandler) + queue.put(port) + server.serve_forever() + + class TestWebHdfsErrors: @pytest.fixture def mock_bad_server( self, remote_file_data: RemoteFileData ) -> Generator[str, None, None]: """Start a bad WebHDFS server that returns invalid JSON""" - - def run_bad_server(queue: Queue[int]) -> None: - class BadHandler(BaseHTTPRequestHandler): - def do_GET(self): - parsed = urllib.parse.urlparse(self.path) - query = urllib.parse.parse_qs(parsed.query) - - if query.get("op") == ["GETFILESTATUS"]: - self.send_response(200) - self.send_header("Content-Type", "application/json") - self.end_headers() - # Missing "length" field - response = json.dumps({}) - self.wfile.write(response.encode()) - else: - self.send_response(400) - self.end_headers() - - def log_message(self, format, *args): - pass - - port = utils.find_free_port() - server = HTTPServer((utils.localhost(), port), BadHandler) - queue.put(port) - server.serve_forever() - queue: Queue[int] = Queue() server_process = Process(target=run_bad_server, args=(queue,), daemon=True) server_process.start()