Skip to content

CASSPYTHON-3 Introduce pyproject.toml to explicitly declare build dependencies#1264

Merged
absurdfarce merged 1 commit intoapache:trunkfrom
absurdfarce:python1428
Feb 5, 2026
Merged

CASSPYTHON-3 Introduce pyproject.toml to explicitly declare build dependencies#1264
absurdfarce merged 1 commit intoapache:trunkfrom
absurdfarce:python1428

Conversation

@absurdfarce
Copy link
Contributor

Original motivation is CASSPYTHON-3, a lifted version of PYTHON-1428. These changes also take care of PYTHON-1334 ("Consider migrating dependency, other information in setup.py into pyproject.toml") and PYTHON-1421 ("Adapt setup.py to deprecation of distutils")

This PR represents a pretty significant re-working of setup.py so the question of what kind of release it belongs in is a very real one. The original motivation was to fix a specific problem with the wheels for 3.29.3 but so far I haven't found a way to do that other than the mechanism outlined here. That question is being considered on CASSPYTHON-3; we'll use this PR to discuss and review the changes themselves.

@absurdfarce absurdfarce marked this pull request as draft December 23, 2025 17:28
@absurdfarce
Copy link
Contributor Author

Ping @millerjp as well

@absurdfarce
Copy link
Contributor Author

I've run this through our wheel build process for Python 3.12 against ARM64 and x86_64 Ubuntu. In both cases all expected native code was preserved.

Might still have a problem though:

(wheel-validate-venv) mersault@stravinsky:~/Downloads$ python3
Python 3.12.11 (main, Sep  2 2025, 14:20:15) [Clang 20.1.4 ] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from cassandra.cmurmur3 import murmur3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: /home/mersault/Downloads/wheel-validate-venv/lib/python3.12/site-packages/cassandra/cmurmur3.cpython-312-aarch64-linux-gnu.so: undefined symbol: rotl64
>>> import cassandra.io.libevwrapper as libev
>>>

I mean... at least libev worked...

@absurdfarce
Copy link
Contributor Author

I removed the the "-s" arg to gcc from CFLAGS in order to diagnose the problem:

$ git show HEAD
commit 2d6517477b44cce69faa7c1b9e08b5525c2eb15e (HEAD -> python1428, origin/python1428)
Author: absurdfarce <bret.mcguire@ibm.com>
Date:   Tue Dec 23 13:21:34 2025 -0600

    Temporarily remove stripping of symbols from shared objects in order to try and diagnose an issue

diff --git a/.github/workflows/build_wheels_linux.yml b/.github/workflows/build_wheels_linux.yml
index c8d5af1..88628ef 100644
--- a/.github/workflows/build_wheels_linux.yml
+++ b/.github/workflows/build_wheels_linux.yml
@@ -13,7 +13,6 @@ jobs:
       CIBW_TEST_REQUIRES: pytest mock pure-sasl eventlet
       CIBW_TEST_COMMAND: echo "wheel is installed"
       CIBW_BEFORE_ALL: yum install -y libev libev-devel
-      CIBW_ENVIRONMENT: CFLAGS='-s'
       CIBW_BUILD_VERBOSITY: 3
     steps:
       - uses: actions/checkout@v4

and the problem... went away?!?!?!?!?!

@absurdfarce
Copy link
Contributor Author

Worth noting that the wheels for 3.29.3 available from pypi also have this issue. Wheels for 3.29.2 from pypi do not have this problem. We introduced the stripping of symbols on 7/30/2024 as the fix for PYTHON-1387. Weird thing is that 3.29.2 was released in September of 2024 so wheels built there would've included that fix.... and they appear fine.

I can't really explain any of this yet. The error itself doesn't make sense... and the chronology doesn't introduce an obvious point where a regression like this could've come in.

@absurdfarce
Copy link
Contributor Author

Moving discussion about the wheel build issue to a distinct issue on python-driver-wheels. Let's keep this PR focused on conversation about the pyproject.toml conversion.

@absurdfarce
Copy link
Contributor Author

I've confirmed that these changes (combined with an equivalent set of changes for python-driver-wheels) appear to build valid wheels (including the Cython modules that were missing in CASSPYTHON-3) for all platforms.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the build configuration from a setup.py-centric approach to a modern pyproject.toml-based approach, addressing deprecation of distutils and explicitly declaring build dependencies. The changes significantly refactor setup.py by removing deprecated distutils imports, custom build commands, and pre-build checks while moving project metadata into pyproject.toml.

Changes:

  • Introduced pyproject.toml with build-system configuration, project metadata, and custom tool configuration for build options
  • Refactored setup.py to remove distutils usage, simplify extension building logic, and read configuration from pyproject.toml
  • Removed custom commands (DocCommand), pre-build checks, and complex error handling from setup.py

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 17 comments.

File Description
pyproject.toml New file defining build system requirements, project metadata, dependencies, and custom cassandra-driver build configuration options
setup.py Significantly simplified to focus on extension building, now reads configuration from pyproject.toml and removes deprecated distutils usage

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

pyproject.toml Outdated
@@ -0,0 +1,57 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "Cython>=3.0", "toml", "cassandra-driver"]
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical circular dependency issue: The build-system requires "cassandra-driver" as a build dependency, but this is the package being built. This will cause build failures as pip cannot install cassandra-driver before building cassandra-driver. The "cassandra-driver" entry should be removed from the requires list.

Suggested change
requires = ["setuptools", "Cython>=3.0", "toml", "cassandra-driver"]
requires = ["setuptools", "Cython>=3.0", "toml"]

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this one; I was really confused when I had to do this to make things work. The underlying problem here is that setup.py gets version from the "cassandra" package... so it needs this package to already be installed, which is indeed circular. Problem here is how to get around that: we want version to be handled canonically in pyproject.toml now but we also clearly need that to be accessible in Python code as well.

Originally I fixed this by extracting version from the pyproject.toml code we're already parsing in setup.py but this isn't really satisfying... most notably it doesn't make the version available via cassandra.version. Next step was to switch over to the importlib.metadata package... that seemed to do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FTR I verified that the importlib.metadata approach works with both local install + wheel builds/installs

setup.py Outdated
class build_extensions(build_ext):

error_message = """
windows_compmile_error = \
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error: "compmile" should be "compile".

Suggested change
windows_compmile_error = \
windows_compile_error = \

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. This was a holdover from when we had compilation in setup.py so this just got removed outright.

try_murmur3 = driver_project_data["build-murmur3-extension"]
try_libev = driver_project_data["build-libev-extension"]
try_cython = driver_project_data["build-cython-extensions"] and not is_pypy

Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored code removes support for command-line arguments that were present in the original: --no-murmur3, --no-libev, --no-cython, and --no-extensions. This is a breaking change that could affect users and build scripts that relied on these flags to control extension building. Consider either maintaining backward compatibility or documenting this breaking change clearly.

Suggested change
# Backwards-compatible support for legacy command-line flags that used to
# control extension building. These flags override the configuration in
# pyproject.toml and are removed from sys.argv so that setuptools and other
# tooling do not see them.
legacy_flag_map = {
"--no-murmur3": "murmur3",
"--no-libev": "libev",
"--no-cython": "cython",
"--no-extensions": "all",
}
cleaned_argv = []
for _arg in sys.argv:
flag_type = legacy_flag_map.get(_arg)
if flag_type is None:
cleaned_argv.append(_arg)
continue
if flag_type == "murmur3":
try_murmur3 = False
elif flag_type == "libev":
try_libev = False
elif flag_type == "cython":
try_cython = False
elif flag_type == "all":
try_murmur3 = False
try_libev = False
try_cython = False
sys.argv = cleaned_argv

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that these options have been removed but this was done by design. The same functionality is available via binary vals in "tool.cassandra-driver" in pyproject.toml. One of the wins of going to pyproject.toml is that the build gets more declarative, and if we're going to do that we should be steering users away from CLI flags defined at build time that modify behaviour.

Comment on lines 178 to 127
setup(
version = __version__,
ext_modules = build_extension_list()
)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored code removes the check that warned users if the old standalone cqlengine package was installed alongside the driver. This warning helped prevent conflicts since cqlengine is now integrated into the driver. Removing this check could lead to confusing behavior for users who have both packages installed. Consider whether this warning is still needed or if it can be safely removed.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explicitly removed this check because cqlengine as a standalone project is ancient and unlikely to still be in use. It is still on pypi, however, so I suppose there's at least an argument here. Still, this feels like the kind of thing we were in the habit of supporting forever that we shouldn't aim to support indefinitely going forward.

@bschoening any thoughts on this one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@absurdfarce I agree with your view. The cqlengine package on PyPi hasn't been updated since 2015 and only supported Python 2.7. Unlikely it would work with Python 3.10+

pyproject.toml Outdated
@@ -0,0 +1,57 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "Cython>=3.0", "toml", "cassandra-driver"]
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build-system requires "Cython>=3.0" unconditionally, but in the original code, Cython was only added to setup_requires when actually needed (when try_cython was True and pre_build_check passed). This means Cython will now always be installed during the build process, even if the user doesn't want to build Cython extensions. This could increase build time and dependencies unnecessarily. Consider making Cython an optional build dependency or documenting why it's now always required.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that this is a change but I think it's much less significant than what Copilot describes. The user merely has to remove cython from build-system.requires in pyproject.toml to achieve the same effect.

More broadly I think our old approach here is worth a rethink anyway. Using cython is the default for all builds (users have to explicitly opt out of it) and it's included for all wheel builds, so the only way a user could avoid cython would be to create a custom local build and share the corresponding wheel. There's a decent argument to be made that we should remove any of the "pure Python" alternates for functionality implemented by cython and just use cython all the time. This PR is the wrong place to do that but it's something to consider.

The other relevant point here is the pending move to Rust for this functionality (see CASSPYTHON-8). A similar conversation should be had there; once we have Rust impls working I don't think it makes sense to keep the "pure Python" versions around.

But again that's a conversation for the future. For now I'm comfortable saying the user can accomplish this by modifying pyproject.toml locally.

Comment on lines 178 to 127
setup(
version = __version__,
ext_modules = build_extension_list()
)
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refactored code removes the DocCommand custom command that was used to generate and test documentation via python setup.py doc or python setup.py doc --test. This is a breaking change that could affect documentation build workflows. Consider either maintaining this functionality or documenting the migration path for documentation building.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're in the process of moving to new documentation tooling and (more to the point) getting this functionality out of the main build.

setup.py Outdated
has_subprocess = False
from pathlib import Path
from setuptools import setup, Extension
import toml
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code imports and uses the toml library, which is also listed as a build dependency. However, for Python 3.11+, the standard library includes tomllib for reading TOML files. For Python 3.9-3.10, the recommended approach is to use tomli (the backport of tomllib) rather than the older toml package. Consider using tomllib (with tomli as a fallback for older Python versions) for better compatibility and future-proofing.

Suggested change
import toml
try:
import tomllib as toml
except ImportError:
import tomli as toml

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bit more to it than what's posted above... I've implemented the same idea in a new commit.

setup.py Outdated
Comment on lines 172 to 173
except Exception:
sys.stderr.write("Failed to cythonize one or more modules. These will not be compiled as extensions (optional).\n")
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception handler at line 172 catches all exceptions with a bare except Exception:, which will silently suppress all errors during Cythonization. While this matches the original behavior, it would be better to log the actual exception details (e.g., using sys.stderr.write(str(exc))) to help users diagnose build issues rather than just printing a generic message.

Suggested change
except Exception:
sys.stderr.write("Failed to cythonize one or more modules. These will not be compiled as extensions (optional).\n")
except Exception as exc:
sys.stderr.write("Failed to cythonize one or more modules. These will not be compiled as extensions (optional).\n")
sys.stderr.write("Cythonization error: %s\n" % (exc,))

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed; I've actually had to make a change very much like this in a working branch or two to diagnose various Cython issues.

setup.py Outdated
Comment on lines 138 to 140
try_murmur3 = driver_project_data["build-murmur3-extension"]
try_libev = driver_project_data["build-libev-extension"]
try_cython = driver_project_data["build-cython-extensions"] and not is_pypy
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for try_murmur3 and try_libev does not respect the try_extensions flag. These extensions could be built even when try_extensions is False (e.g., on unsupported platforms). These variables should be combined with try_extensions similar to how it was done in the original code. For example: try_murmur3 = try_extensions and driver_project_data["build-murmur3-extension"].

Suggested change
try_murmur3 = driver_project_data["build-murmur3-extension"]
try_libev = driver_project_data["build-libev-extension"]
try_cython = driver_project_data["build-cython-extensions"] and not is_pypy
try_murmur3 = try_extensions and driver_project_data["build-murmur3-extension"]
try_libev = try_extensions and driver_project_data["build-libev-extension"]
try_cython = try_extensions and driver_project_data["build-cython-extensions"] and not is_pypy

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old code also wouldn't allow extensions to be built on unsupported platforms but we do have something of a disconnect here in that we can't use "try_extensions" to short-circuit the builds of each individual extension. To avoid confusion between the interaction of "try_extensions" and, say, "try_cython" I thought it best to remove "try_extensions" all together. Users can enable or disable individual extensions by setting the corresponding individual binary flags in pyproject.toml.

…endencies

patch by Bret McGuire; reviewed by Bret McGuire and Brad Schoening
reference: apache#1264
Comment on lines 178 to 127
setup(
version = __version__,
ext_modules = build_extension_list()
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@absurdfarce I agree with your view. The cqlengine package on PyPi hasn't been updated since 2015 and only supported Python 2.7. Unlikely it would work with Python 3.10+

@bschoening
Copy link
Contributor

Looks good +1

@absurdfarce
Copy link
Contributor Author

Excellent, thanks @bschoening! I'll squash and merge directly.

@absurdfarce absurdfarce merged commit 3c13176 into apache:trunk Feb 5, 2026
@absurdfarce absurdfarce deleted the python1428 branch February 5, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants