diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8b8fc0e791..82bf21e52f 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -40,6 +40,19 @@ When modifying driver files, rebuilding Cython modules is often necessary. Without caching, each such rebuild may take over a minute. Caching usually brings it down to about 2-3 seconds. +**Important:** After modifying any ``.py`` file under ``cassandra/`` that is +Cython-compiled (such as ``query.py``, ``protocol.py``, ``cluster.py``, etc.), +extensions must be rebuilt before running tests. If you always use ``uv run`` +(e.g. ``uv run pytest``), this is handled automatically via the ``cache-keys`` +configuration in ``pyproject.toml``. If you invoke ``pytest`` directly, you can +rebuild with:: + + uv sync --reinstall-package scylla-driver + +Without rebuilding, Python will load the stale compiled extension (``.so`` / ``.pyd``) +instead of your modified ``.py`` source, and your changes will not actually be tested. +The test suite will emit a warning if it detects this situation. + Building the Docs ================= diff --git a/docs/installation.rst b/docs/installation.rst index 7b4823b832..fbb9ac4043 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -62,9 +62,6 @@ threads used to build the driver and any C extensions: .. code-block:: bash - $ # installing from source - $ CASS_DRIVER_BUILD_CONCURRENCY=8 python setup.py install - $ # installing from pip $ CASS_DRIVER_BUILD_CONCURRENCY=8 pip install scylla-driver Note that by default (when CASS_DRIVER_BUILD_CONCURRENCY is not specified), concurrency will be equal to the number of @@ -108,7 +105,7 @@ installed. You can find the list of dependencies in Once the dependencies are installed, simply run:: - python setup.py install + pip install . (*Optional*) Non-python Dependencies @@ -122,9 +119,9 @@ for token-aware routing with the ``Murmur3Partitioner``, `libev `_ event loop integration, and Cython optimized extensions. -When installing manually through setup.py, you can disable both with -the ``--no-extensions`` option, or selectively disable them with -with ``--no-murmur3``, ``--no-libev``, or ``--no-cython``. +Extensions can be selectively disabled using environment variables: +``CASS_DRIVER_NO_EXTENSIONS=1`` (disable all), ``CASS_DRIVER_NO_CYTHON=1``, +or ``CASS_DRIVER_NO_LIBEV=1``. To compile the extensions, ensure that GCC and the Python headers are available. @@ -149,31 +146,25 @@ This is not a hard requirement, but is engaged by default to build extensions of pure Python implementation. This is a costly build phase, especially in clean environments where the Cython compiler must be built -This build phase can be avoided using the build switch, or an environment variable:: +This build phase can be avoided using an environment variable:: - python setup.py install --no-cython + CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver -Alternatively, an environment variable can be used to switch this option regardless of +Alternatively, the environment variable can be used to switch this option regardless of context:: CASS_DRIVER_NO_CYTHON=1 - or, to disable all extensions: CASS_DRIVER_NO_EXTENSIONS=1 -This method is required when using pip, which provides no other way of injecting user options in a single command:: - - CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver - CASS_DRIVER_NO_CYTHON=1 sudo -E pip install ~/python-driver - -The environment variable is the preferred option because it spans all invocations of setup.py, and will +These environment variables are the preferred option, and will prevent Cython from being materialized as a setup requirement. -If your sudo configuration does not allow SETENV, you must push the option flag down via pip. However, pip -applies these options to all dependencies (which break on the custom flag). Therefore, you must first install -dependencies, then use install-option:: +If your sudo configuration does not allow SETENV, you must first install +dependencies, then install the driver:: sudo pip install futures - sudo pip install --install-option="--no-cython" + sudo CASS_DRIVER_NO_CYTHON=1 pip install scylla-driver Supported Event Loops @@ -205,7 +196,7 @@ install libev using any Windows package manager. For example, to install using $ vcpkg install libev If successful, you should be able to build and install the extension -(just using ``setup.py build`` or ``setup.py install``) and then use +(just using ``pip install .``) and then use the libev event loop by doing the following: .. code-block:: python diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000000..8fd2fc923b --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,67 @@ +# Copyright ScyllaDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.machinery +import os +import warnings + +# Directory containing the Cython-compiled driver modules. +_CASSANDRA_DIR = os.path.join(os.path.dirname(__file__), os.pardir, "cassandra") + + +def pytest_configure(config): + """Warn when a compiled Cython extension is older than its .py source. + + Python's import system prefers compiled extensions (.so / .pyd) over pure + Python (.py) files. If a developer edits a .py file without rebuilding + the Cython extensions, the tests + will silently run the *old* compiled code, masking any regressions in the + Python source. + + This hook detects such staleness at test-session startup so the developer + is alerted immediately. + """ + stale = [] + # Iterate over .py sources and, for each module, look for the first + # existing compiled extension in EXTENSION_SUFFIXES order. This mirrors + # how Python's import machinery selects an extension module, and avoids + # globbing patterns like "*{suffix}" that can pick up ABI-tagged + # extensions built for other Python versions. + if os.path.isdir(_CASSANDRA_DIR): + for entry in os.listdir(_CASSANDRA_DIR): + if not entry.endswith(".py"): + continue + module_name, _ = os.path.splitext(entry) + py_path = os.path.join(_CASSANDRA_DIR, entry) + # For this module, find the first extension file Python would load. + for suffix in importlib.machinery.EXTENSION_SUFFIXES: + ext_path = os.path.join(_CASSANDRA_DIR, module_name + suffix) + if not os.path.exists(ext_path): + continue + if os.path.getmtime(py_path) > os.path.getmtime(ext_path): + stale.append((module_name, ext_path, py_path)) + # Only consider the first matching suffix; this is the one + # the import system would actually use. + break + + if stale: + names = ", ".join(m for m, _, _ in stale) + warnings.warn( + f"Stale Cython extension(s) detected: {names}. " + f"The .py source is newer than the compiled extension — tests " + f"will run the OLD compiled code, not your latest changes. " + f"Rebuild with: uv sync --reinstall-package scylla-driver\n" + f"Or use 'uv run pytest' which handles rebuilds automatically.", + stacklevel=1, + )