Skip to content

Native CI: a cheap Python-version job (3.11, 3.13, 3.14, 3.14t) on the built wheel - #68

Merged
tae898 merged 2 commits into
native-two-distfrom
native-ci-python-versions
Oct 25, 2026
Merged

tae898 merged 2 commits into
native-two-distfrom
native-ci-python-versions

Conversation

@tae898

@tae898 tae898 commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Native parity on every supported Python, cheaply, in the selective-matrix design of test-native.yml. The native wheel is py3-none (a ctypes loader, no per-interpreter extension), so one built wheel can be installed into every Python without a rebuild. This is the redo of humemai/arcadedb-embedded-python#540 for this repository (that PR stays as a reference).

What changes

scripts/select_native_matrix.py gets a fourth output, pyver=<json>: the supported Pythons that the full parity run of the event leaves out, plus the free-threaded 3.14t. test-native.yml gets a pyver job that runs those legs. So nothing is tested twice and the load stays small:

event full parity (test job, every platform) pyver legs (linux-x86_64 wheel, subset)
pull request 3.12 3.11, 3.13, 3.14, 3.14t
push to main, nightly, dispatch 3.12, 3.14 3.11, 3.13, 3.14t
release (release-native.yml calls the workflow with release: true) 3.11, 3.12, 3.13, 3.14 3.14t

The pyver job has needs: [plan, build], runs on ubuntu-latest with fail-fast: false and a 20 minute timeout, downloads the native-wheel-linux-x86_64 artifact of the build job (no rebuild) and runs uv run --no-project --python <ver> --with <wheel> ... native/wheel/parity_wheel.py --files <subset>. The gate is a copy of the test job's awk (no failure, error, crash, timeout or uncounted skip; both copies carry a keep-in-sync comment). Actions are pinned by the SHAs the file already uses. A step prints sys._is_gil_enabled() after loading the wheel and numpy, pandas, pyarrow and jpype1, and warns on the 3.14t leg if the GIL got re-enabled.

It is informational: release-native.yml (verify and publish) reads only the native-passed-* markers and the native-wheel-* wheels and no job waits for pyver (a unit test pins both), and the workflow header and the job comment say so. A red leg is visible but never blocks a release. Promote it once it has been green for a while.

The subset (13 files, 14 where polars installs)

test_core, test_native_udf (ctypes layer, database lifecycle, callbacks), test_resultset (queries and results), test_parameter_binding, test_type_conversion, test_transaction_config (transactions), test_async_executor, test_concurrency and test_free_threading (threads; the second file is written for a free-threaded CPython and also passes with the GIL), test_vector, test_numpy_support, test_resultset_arrow, test_columnar_readers (numpy, pandas, arrow), and test_dataframe_ingest (pandas, polars and arrow ingest) on the legs that have polars. The files are self-contained, fast, and fail early on an interpreter-specific problem. The 70-file suite stays in the test job.

3.14t: included, without polars

All dependencies of the subset have cp314t wheels (numpy 2.5.4, pandas 3.1.0, pyarrow 26.0.0, jpype1 1.7.1, requests, pytest), the wheel and its ctypes layer load and run on the free-threaded build, and the GIL stays off after the wheel and those packages are loaded, so the leg tests free threading for real. The one exception is polars: polars and polars-runtime-32 ship abi3 wheels only, which cannot be installed on a free-threaded build, and test_dataframe_ingest.py imports polars at module level (pytest.importorskip; this repository accepts no skips). The 3.14t leg therefore installs no polars and leaves that file out; the matrix carries polars: false for it, and a unit test pins polars == not freethreaded. If you prefer the strict reading (any missing cp314t wheel means no 3.14t leg), drop FREE_THREADED_PYTHONS in the script.

3.14t is not a classifier of the wheel, so ALL_PYTHONS (checked against native/wheel/pyproject.toml) is unchanged, and I did not add a "Free Threading" trove classifier (a support claim; the optional polars extra cannot be installed there).

Merge order: this lands after #69

tests/test_async_executor.py::test_per_record_callback_runs_before_the_batch_commits_and_on_ok_after aborts the whole process (Fatal Python error: Aborted, in wait_completion through lib.call) against every native image built before the glue of #69 (the nbatch image, the two-dist image and the whl-pre wheel alike). It is the same on 3.11, 3.12, 3.13, 3.14 and 3.14t, so it is not a Python-version problem: it is the glue/image mismatch that #69 fixes. I kept test_async_executor.py in the subset (async is part of what this checks), so the pyver legs show test_async_executor.py(CRASH) and go red on an image without #69. Therefore #68 lands after #69; the other 12 or 13 files pass on every Python.

Local results

Rebased onto native-two-dist after the workflow rename (native.yml is now test-native.yml, the release jobs live in release-native.yml): the pyver job moved into test-native.yml unchanged in behaviour, and tests/test_select_native_matrix.py, tests/test_select_matrix.py and tests/test_check_version.py pass on the rebased head, as do actionlint and pre-commit on the touched files. The pyver job needs no --with setuptools (no file of the subset builds with setup.py). The measurements below are from before the rebase.

Run from the repository root against the existing nbatch image (ARCADEDB_BACKEND=native, ARCADEDB_NATIVE_LIB=.../nbatch/libarcadedb.so, PYTHONPATH=src, taskset -c 8-15), one pytest process per file, uv run --no-project --python <ver> --with pytest --with numpy --with requests --with jpype1 --with pyarrow --with pandas [--with polars]. All files other than the aborting one passed on every version; test_async_executor.py with that one test deselected passes 20 of 20 on every version.

Python files passed (of 14, or 13 on 3.14t) tests passed skipped wall (without async)
3.11.15 13 of 14 268 32 25 s
3.12.13 (reference) 13 of 14 268 32 26 s
3.13.13 13 of 14 268 32 25 s
3.14.5 13 of 14 268 32 27 s
3.14.5t 12 of 13 199 32 22 s

The skips are the NOTYET and NA-JVM categories of tests/conftest.py, which the parity runner counts separately. I also replayed the job's run steps verbatim (uv invocation, parity_wheel.py --files, the GIL step, the awk gate) against an already-built wheel (arcadedb-26.11.1.dev0-py3-none-manylinux_2_34_x86_64.whl from another worktree, no rebuild) on 3.14t (199 passed, 0 failures, GIL stays off) and 3.11 (268 passed, 0 failures); both show the gate failing on test_async_executor.py(CRASH) as described above, which also confirms that the gate catches a crash. tests/test_select_native_matrix.py (9 tests, 3 new) passes, actionlint, pre-commit and scripts/check_no_embedded_name.py pass on the touched files. docs/modules/ROOT/pages/development/ci.adoc describes the job.

Not verified

The workflow itself cannot be run locally: the artifact download, the 3.14t interpreter download with the pinned uv 0.12.0 (I used uv 0.13.4 locally), the fromJSON matrix from the plan job, the runner's core count and the real CI timing are untested until the first run on this branch. I expect roughly 3 to 5 minutes per leg, mostly downloads (numpy, pandas and pyarrow are about 80 MB); that is a guess.

🤖 Generated with Claude Code

… (3.11, 3.13, 3.14, 3.14t)

select_native_matrix.py prints a pyver list: the supported Pythons the full parity run of the event leaves out, plus the free-threaded 3.14t. The new pyver job of test-native.yml installs the already built linux-x86_64 wheel (py3-none, no rebuild) into each and runs 13 test files (14 where polars installs) through native/wheel/parity_wheel.py --files, with the same gate as the test job.

The job is informational: release-native.yml reads only the native-passed-* markers and the native-wheel-* wheels, so a red pyver leg never blocks a release (a test pins that). 3.14t runs without polars, which has no free-threaded wheel, and so without test_dataframe_ingest.py. A step reports whether the GIL stays off.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@tae898
tae898 force-pushed the native-ci-python-versions branch from a63d0b1 to 7f88779 Compare October 25, 2026 12:29
@tae898
tae898 marked this pull request as ready for review October 25, 2026 13:40
…) into native-ci-python-versions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tae898
tae898 merged commit 42b0b64 into native-two-dist Oct 25, 2026
27 checks passed
@tae898
tae898 deleted the native-ci-python-versions branch October 25, 2026 14:40
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.

1 participant