Skip to content

Unify Python FFI on PyO3 abi3, retire uniffi (5ipk.10)#7

Merged
lavs9 merged 3 commits into
mainfrom
feat/5ipk10-pyo3-abi3
Jul 12, 2026
Merged

Unify Python FFI on PyO3 abi3, retire uniffi (5ipk.10)#7
lavs9 merged 3 commits into
mainfrom
feat/5ipk10-pyo3-abi3

Conversation

@lavs9

@lavs9 lavs9 commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Why

quantwave-python was the last uniffi cdylib: it shipped a py3-none wheel while quantwave-plugins and quantwave-backtest-py shipped cp39-abi3. The custom build_unified_wheel.py existed only to reconcile those mismatched tags — the root cause of the 9gek.1 wheel-tag bug class.

What

Port the entire uniffi surface to PyO3 abi3. The transform swaps only attributes — every struct/enum/fn/field name is preserved — so the Python-visible surface (getattr(_quantwave, ...)) is byte-identical.

uniffi count → PyO3
#[derive(uniffi::Record)] 50 #[pyclass(get_all)] + Clone
#[derive(uniffi::Object)] 35 #[pyclass] + #[pymethods]
#[derive(uniffi::Enum)] 1 #[pyclass(eq, eq_int)]
#[uniffi::export] 90 #[pyfunction] / #[pymethods]
11 declarative macros rewritten to emit PyO3
setup_scaffolding!() explicit #[pymodule] registration

Two symbols uniffi re-cased via heck (BullBearHMMBullBearHmm, OIZonesResultOiZonesResult) are pinned with #[pyclass(name = ...)]. Two named constructors (bull_bear, from_params) map to #[staticmethod] (classmethod-style), not #[new].

Now all three crates build cp39-abi3 → the wheel-tag bug is killed at the source, and the merge script becomes a trivial abi3 .so bundler with a uniform-tag assertion.

Also:

  • Move backtest.rs into quantwave-backtest-py (delete the cross-crate [lib] path).
  • generate_api_stubs.py: detect streaming classes via #[pyclass] + inner field.
  • Drop dead uniffi-bindgen from pyproject build-requires, CI, release, and local verify scripts.
  • Docs: UniFFI → PyO3 (roadmap, validation).

Scope

Incremental "abi3-everywhere first" (per decision): single-crate collapse deferred to a fast-follow bead.

Validation

  • Gold parity 28/28, options parity + doctests 53/53.
  • Unified wheel installs clean in a fresh venv: cp39-abi3, 3 abi3 .so, 221 indicators.
  • pypi_smoke_test.py passes on the unified wheel.
  • All 15 CI sanity checks green; rustfmt clean; workspace builds.

Known pre-existing (NOT introduced here)

test_frac_diff.py (2) and test_python_dx.py::TestTalib::{test_list_functions,test_rsi_callable} (2) fail identically on main/uniffi — a pre-existing top-level DX/registry binding bug (tracked in a separate bead). Verified against a main worktree build.

🤖 Generated with Claude Code

lavs9 and others added 3 commits July 11, 2026 15:28
…k.10)

quantwave-python was the last uniffi cdylib: it shipped a py3-none wheel
while quantwave-plugins and quantwave-backtest-py shipped cp39-abi3. The
custom build_unified_wheel.py existed only to reconcile those mismatched
tags — the root cause of the 9gek.1 wheel-tag bug class.

Port the entire uniffi surface (50 Records, 35 streaming Objects, 1 Enum,
90 exports, 11 declarative macros) to PyO3 abi3. The transform swaps only
attributes — every struct/enum/fn/field name is preserved — so the
Python-visible surface (getattr(_quantwave, ...)) is byte-identical. The
two symbols uniffi re-cased via heck (BullBearHMM->BullBearHmm,
OIZonesResult->OiZonesResult) are pinned with #[pyclass(name = ...)].

Now all three crates build cp39-abi3 → the wheel-tag bug is killed at the
source and the merge script becomes a trivial abi3 .so bundler (with a
uniform-tag assertion guarding against future non-abi3 regressions).

Also:
- move backtest.rs into quantwave-backtest-py (delete the cross-crate
  [lib] path = ../quantwave-python/src/backtest.rs)
- generate_api_stubs.py: detect streaming classes via #[pyclass] + inner
  field (honoring name attrs) instead of #[derive(uniffi::Object)]
- drop uniffi-bindgen from pyproject build-requires, ci.yml, release.yml
- docs: UniFFI -> PyO3 (roadmap, validation)

Incremental scope (user decision): single-crate collapse deferred to a
fast-follow bead. Validated: gold parity 28/28, options parity + doctests
53/53, unified wheel installs clean (cp39-abi3, 3 abi3 .so, 221
indicators), all 15 CI sanity checks green, rustfmt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…i-bindgen

Pre-push wheel smoke caught two named uniffi constructors that the blanket
`#[uniffi::constructor]` -> `#[new]` transform mis-converted: BullBearHMM::
bull_bear and GaussianHmmFilterPy::from_params. uniffi exposed these named
constructors as classmethods (Class.bull_bear()), so they map to
`#[staticmethod]`, not `#[new]` (which would only allow Class()).

Also remove the now-dead `uniffi-bindgen==0.31.0` install from the local
verify path (quantwave_verify.sh, git-hooks/pre-push, verify_cache.py).

Verified: BullBearHmm.bull_bear().next() and build_feature_matrix work;
full unified-wheel pypi_smoke_test passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…fracdiff)

The dynamic top-level population only bound indicators by SLUG (qw.frac_diff),
so the native streaming-class and batch-fn names documented as available
(qw.FracDiff, qw.fracdiff, qw.rsi, qw.SuperTrend) were missing — a pre-existing
gap left when the old hardcoded `class ta:` aliases were replaced by slug-only
population. (Confirmed failing identically on main/uniffi, so not a 5ipk.10
regression; surfaced while validating the PyO3 migration.)

Restore native-name binding by iterating dir(_quantwave): bind public callables
(classes + batch fns) not already defined, skipping result records/errors
(_is_internal_symbol) and the deprecated options helpers (_OPTIONS_SYMBOLS stay
routed through __getattr__ so qw.bs_call_price still warns). Auto-syncs as new
indicators are added.

Fixes test_frac_diff.py (3/3). No regressions: gold parity, options deprecation,
metadata codegen, streaming readiness all green (97 passed). The 2 remaining
talib failures (test_list_functions/test_rsi_callable) are a distinct TA-Lib
map-completeness bug, tracked separately.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lavs9
lavs9 merged commit 34f9877 into main Jul 12, 2026
7 checks passed
@lavs9
lavs9 deleted the feat/5ipk10-pyo3-abi3 branch July 12, 2026 07:31
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