Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
138 changes: 138 additions & 0 deletions .github/workflows/build-scylla-driver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# SPDX-FileCopyrightText: 2026 The RISE Project
# SPDX-License-Identifier: MIT
---
# This workflow is based on: https://github.com/scylladb/python-driver/blob/master/.github/workflows/lib-build.yml
name: Build scylla-driver wheels (riscv64)

on:
workflow_dispatch:
inputs:
version:
description: 'Version glob to (re)build; empty builds every version of docs/packages/scylla-driver.yaml not released yet'
required: false
default: ''
pull_request:
branches: [main]
paths:
- '.github/workflows/build-scylla-driver.yml'
- 'docs/packages/scylla-driver.yaml'
- 'patches/scylla-driver/**'
push:
branches: [main]
paths:
- '.github/workflows/build-scylla-driver.yml'
- 'docs/packages/scylla-driver.yaml'
- 'patches/scylla-driver/**'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

permissions:
contents: read # to fetch code (actions/checkout)

env:
MANYLINUX_RISCV64_IMAGE: quay.io/pypa/manylinux_2_39_riscv64

jobs:
setup:
uses: $/.github/workflows/_setup.yml
with:
package: scylla-driver
version: ${{ inputs.version }}

build_wheels:
needs: [setup]
if: needs.setup.outputs.versions != '[]'
name: Build scylla-driver ${{ matrix.version }} ${{ matrix.python }}-manylinux_riscv64
runs-on: ubuntu-24.04-riscv
timeout-minutes: 240
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
python:
- "cp312"
- "cp313"
- "cp314"
- "cp314t"

env:
SCYLLA_DRIVER_VERSION: ${{ matrix.version }}

steps:
- name: Checkout scylla-driver ${{ env.SCYLLA_DRIVER_VERSION }}
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: scylladb/python-driver
ref: ${{ env.SCYLLA_DRIVER_VERSION }}-scylla
persist-credentials: false

- name: Checkout python-wheels
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: python-wheels
persist-credentials: false

# auditwheel bundles the image's libev, whose notice the wheel would otherwise drop.
- name: Patch scylla-driver source
run: git apply python-wheels/patches/scylla-driver/${{ env.SCYLLA_DRIVER_VERSION }}/00*.patch

- name: Build wheels
uses: pypa/cibuildwheel@1828c10ab37f080699c7b81cea34097c684a7074 # v4.2.0
with:
output-dir: wheelhouse/
only: ${{ matrix.python }}-manylinux_riscv64
env:
CIBW_MANYLINUX_RISCV64_IMAGE: ${{ env.MANYLINUX_RISCV64_IMAGE }}
CIBW_BUILD_FRONTEND: build # upstream's build[uv] needs a host uv the runner has none of (gotcha 13)
# Upstream's before-build, minus the AlmaLinux key and the packages Rocky 10 preinstalls.
CIBW_BEFORE_BUILD: rm -rf ~/.pyxbld && yum install -y libev libev-devel
# The three CASS_DRIVER/CFLAGS values are upstream's [tool.cibuildwheel] environment,
# which this replaces; EXTENSIONS_ARE_MUST is what fails a degraded pure-Python build.
CIBW_ENVIRONMENT: >-
CASS_DRIVER_BUILD_CONCURRENCY=2
CASS_DRIVER_BUILD_EXTENSIONS_ARE_MUST=yes
CFLAGS='-g0 -O3'
PIP_EXTRA_INDEX_URL=https://pypi.riseproject.dev/simple/
# Upstream's dev group, minus ccm (a git dependency only the integration tests use)
# and minus gevent on cp314t, which it has no build for. setuptools is what gives
# pyximport a distutils on 3.12+ (gotcha 211).
CIBW_TEST_GROUPS: ''
CIBW_TEST_REQUIRES: >-
pytest~=8.0 PyYAML pure-sasl twisted[tls] eventlet>=0.33.3 cython>=3.2
packaging>=25.0 futurist numpy objgraph setuptools
${{ matrix.python == 'cp314t' && '' || 'gevent' }}
# PIP_PREFER_BINARY keeps pip on our registry's gevent instead of compiling
# PyPI's newer release.
CIBW_TEST_ENVIRONMENT: PIP_PREFER_BINARY=1
# cassandra/ would shadow the installed wheel, so only the two .pyx files
# tests/unit/cython/types_testhelper.pyx includes by relative path are staged.
CIBW_TEST_SOURCES: tests cassandra/ioutils.pyx cassandra/cython_marshal.pyx
CIBW_TEST_COMMAND: >-
python -c "import importlib.metadata as m; f=[str(p) for p in m.files('scylla-driver')]; assert any(s.endswith('licenses/LICENSE.libev') for s in f), f" &&
python -m pytest tests/unit
${{ matrix.python == 'cp314t' && '' || '&& EVENT_LOOP_MANAGER=gevent python -m pytest tests/unit/io/test_geventreactor.py' }}

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: scylla-driver-${{ env.SCYLLA_DRIVER_VERSION }}-${{ matrix.python }}-manylinux_riscv64
path: wheelhouse/*.whl
if-no-files-found: error

publish:
name: Publish scylla-driver ${{ matrix.version }}
needs: [setup, build_wheels]
if: needs.setup.outputs.versions != '[]'
strategy:
fail-fast: false
matrix:
version: ${{ fromJSON(needs.setup.outputs.versions) }}
permissions:
contents: write
pull-requests: write
uses: $/.github/workflows/_publish-wheel.yml
secrets:
app-private-key: ${{ secrets.RISEPROJECT_APP_PRIVATE_KEY }}
with:
artifact-pattern: scylla-driver-${{ matrix.version }}-*-manylinux_riscv64
6 changes: 6 additions & 0 deletions docs/packages/scylla-driver.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package-name: scylla-driver
source-code: https://github.com/scylladb/python-driver/
license: Apache-2.0
versions:
- version: 3.29.11
patched: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
From ca306846abdb1396e55893b56e5d368975218bf9 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 21 Sep 2026 11:47:03 +0000
Subject: [PATCH] Ship the licence of the bundled libev in the wheel

cassandra.io.libevwrapper links libev dynamically, so auditwheel copies the
system libev.so.4 into scylla_driver.libs/ and rewrites the extension's RPATH
to it: every manylinux wheel redistributes libev in binary form. libev is
dual-licensed BSD-2-Clause OR GPL-2.0-or-later and its BSD terms require the
copyright notice to travel with the binary, but the wheel carries only the
driver's own Apache-2.0 LICENSE.

pyproject.toml declares no license-files, so setuptools' default
LICEN[CS]E* root glob picks the new file up and no packaging change is
needed. The text is the notice from libev 4.33, the version the manylinux
images package.

Upstream-Status: To upstream [not yet submitted; this port may only touch riseproject-dev/python-wheels]
---
LICENSE.libev | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 LICENSE.libev

diff --git a/LICENSE.libev b/LICENSE.libev
new file mode 100644
index 0000000..04b35e7
--- /dev/null
+++ b/LICENSE.libev
@@ -0,0 +1,41 @@
+The libev shared library the cassandra.io.libevwrapper extension links against
+is redistributed inside the binary wheel. libev is not part of this project and
+carries the licence below; the driver's own licence is in LICENSE.
+
+All files in libev are
+Copyright (c)2007,2008,2009,2010,2011,2012,2013 Marc Alexander Lehmann.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Alternatively, the contents of this package may be used under the terms
+of the GNU General Public License ("GPL") version 2 or any later version,
+in which case the provisions of the GPL are applicable instead of the
+above. If you wish to allow the use of your version of this package only
+under the terms of the GPL and not to allow others to use your version of
+this file under the BSD license, indicate your decision by deleting the
+provisions above and replace them with the notice and other provisions
+required by the GPL in this and the other files of this package. If you do
+not delete the provisions above, a recipient may use your version of this
+file under either the BSD or the GPL.
--
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
From ad54ddf761910e54d651b3398cd69abbce3bfb14 Mon Sep 17 00:00:00 2001
From: Ludovic Henry <git@ludovic.dev>
Date: Mon, 21 Sep 2026 16:46:54 +0000
Subject: [PATCH] Restart the libev prepare watcher after the cleanup test

LibevLoop._cleanup() stops the shared prepare watcher ("Stop the prepare
watcher first to prevent race conditions"), and nothing ever starts it again.
test_watchers_are_finished calls that cleanup on the process-wide _global_loop
and then only resets _shutdown, so the loop it hands to the rest of the session
has no prepare watcher: LibevLoop._loop_will_run - and with it _update_timer,
the only caller that arms _loop_timer - never runs again, and TimerManager
timeouts are never serviced.

Every timer created afterwards therefore stays pending forever, and the next
test, LibevTimerTest.test_multi_timer_validation, spins in
submit_and_wait_for_completion's unbounded wait loop until the job is killed.
Restart the watcher alongside the _shutdown reset so the loop is whole again.

Upstream-Status: To upstream [not yet submitted; this port may only touch riseproject-dev/python-wheels]
---
tests/unit/io/test_libevreactor.py | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/tests/unit/io/test_libevreactor.py b/tests/unit/io/test_libevreactor.py
index cf7e7ca..6804290 100644
--- a/tests/unit/io/test_libevreactor.py
+++ b/tests/unit/io/test_libevreactor.py
@@ -87,6 +87,10 @@ class LibevConnectionTest(ReactorTestMixin, unittest.TestCase):
assert conn._read_watcher.stop.mock_calls

_global_loop._shutdown = False
+ # _cleanup() stops the loop's prepare watcher and nothing restarts it, so
+ # LibevLoop._update_timer never runs again and no timer created later in
+ # this process is ever serviced. The timer tests below then wait forever.
+ _global_loop._preparer.start()


class LibevTimerPatcher(unittest.TestCase):
--
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
From 853f22b277e6602a6125f82086b087f711447a5d Mon Sep 17 00:00:00 2001
From: sylwiaszunejko <sylwia.szunejko@gmail.com>
Date: Thu, 18 Jun 2026 12:05:14 +0200
Subject: [PATCH] Fix Session._set_keyspace_for_all_pools to report all
pools' errors

The final callback was invoked with host_errors (the errors from only
the last pool to finish) instead of the accumulated errors dict. If the
last pool succeeded, failures from other pools were silently lost. Pass
the aggregated errors dict, matching the method's docstring.

tests/unit/test_cluster.py::SessionTest::test_set_keyspace_for_all_pools_reports_all_errors ships in the
3.29.11 tag and fails on every architecture; the fix landed on master three days after the
release was cut.

Upstream-Status: Backport [https://github.com/scylladb/python-driver/commit/26c201a74c21d038c2bb4b45f1c074a5f40cad2a]
(cherry picked from commit 26c201a74c21d038c2bb4b45f1c074a5f40cad2a)
---
cassandra/cluster.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/cassandra/cluster.py b/cassandra/cluster.py
index 1181c6f..fdbdfb8 100644
--- a/cassandra/cluster.py
+++ b/cassandra/cluster.py
@@ -3439,7 +3439,7 @@ class Session(object):
errors[pool.host] = host_errors

if not remaining_callbacks:
- callback(host_errors)
+ callback(errors)

for pool in tuple(self._pools.values()):
pool._set_keyspace_for_all_conns(keyspace, pool_finished_setting_keyspace)
--
2.43.0

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
From 3e6b32bb6dded5b4f5bdf1f4975c98f15ec16037 Mon Sep 17 00:00:00 2001
From: Yaniv Michael Kaul <yaniv.kaul@scylladb.com>
Date: Mon, 29 Jun 2026 23:32:46 +0300
Subject: [PATCH] fix: add missing scope validation in
Session.wait_for_schema_agreement

The docstring and test promised ValueError for invalid scope values
(e.g. 'planet'), but the validation was never implemented in the method
body. Add an explicit check against the three SchemaAgreementScope
members.

Introduced in commit 0d215f45b (cluster: add Session.wait_for_schema_agreement).

tests/unit/test_cluster.py::SessionTest::test_wait_for_schema_agreement_rejects_unknown_scope ships in
the 3.29.11 tag and fails on every architecture; the fix landed on master two weeks after the
release was cut.

Upstream-Status: Backport [https://github.com/scylladb/python-driver/commit/03c4c9601580bf433217b54d39d1e804ad3e959a]
(cherry picked from commit 03c4c9601580bf433217b54d39d1e804ad3e959a)
---
cassandra/cluster.py | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/cassandra/cluster.py b/cassandra/cluster.py
index fdbdfb8..795aafe 100644
--- a/cassandra/cluster.py
+++ b/cassandra/cluster.py
@@ -3482,6 +3482,11 @@ class Session(object):
if wait_time is not None and wait_time <= 0:
raise ValueError("wait_time must be greater than 0")

+ if scope not in (SchemaAgreementScope.RACK, SchemaAgreementScope.DC, SchemaAgreementScope.CLUSTER):
+ raise ValueError(
+ "scope must be SchemaAgreementScope.RACK, .DC, or .CLUSTER"
+ )
+
total_timeout = wait_time if wait_time is not None else self.cluster.max_schema_agreement_wait
if total_timeout <= 0:
raise ValueError("total_timeout must be greater than 0")
--
2.43.0

Loading