From cee1dd090c6c5462f2fdfe22195643ece170045d Mon Sep 17 00:00:00 2001 From: Leo Meyerovich Date: Mon, 27 Jul 2026 19:08:15 -0700 Subject: [PATCH] test(gfql): stop test_index.py fabricating 20 failures without cudf_polars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_engines()` appended `"polars-gpu"` when **`cudf`** imported. But `polars-gpu` is the `cudf_polars` GPU collect target and raises ImportError: GFQL engine='polars-gpu' requires the RAPIDS cudf_polars stack without it. So any box with cuDF installed and `cudf_polars` absent — a common developer configuration — got 20 failures indistinguishable from product breakage, in a file that IS listed in `bin/test-polars.sh`. CI never caught it because that lane installs neither package, so the parameter did not exist there: a gate can only fabricate failures where it is never exercised. Measured on a box with cuDF 25.10 and no `cudf_polars`: before 20 failed, 116 passed after 0 failed, 105 passed SCOPE NOTE. This branch originally carried the whole CA4 allowlist change (seven files added to `bin/test-polars.sh`). PR #1805 (`fix/gfql-polars-lane-invisible-tests`), opened concurrently, is a SUPERSET of that list AND adds `test_polars_lane_completeness.py`, a guard that fails when a polars-parametrized file is missing from the lane — which makes the fix permanent rather than one-time. It also lands the same pandas-3 case-mapping xfail, against the same issue. Reduced to the one hunk #1805 does not contain, so the owner reviews the allowlist once instead of twice. RUNTIME DELTA: zero. One test file's engine list and a CHANGELOG line; no pyg-bench lane run required (CB5), checkable from the diff. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB --- CHANGELOG.md | 1 + graphistry/tests/compute/gfql/index/test_index.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac88700713..d96c13e3f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - **Seeded typed-hop property projection keeps its fast path through DISTINCT / ORDER BY / SKIP / LIMIT**: those trailing row ops are plain frame operations, so the fast path now delegates them to the canonical chain instead of declining the whole query. ### Changed +- **The polars-engine index tests stop fabricating 20 failures on a cuDF-without-`cudf_polars` box** (tests only; zero runtime delta). `graphistry/tests/compute/gfql/index/test_index.py` built its engine list by appending `"polars-gpu"` when **`cudf`** imported — but `polars-gpu` is the `cudf_polars` GPU collect target and raises `ImportError: GFQL engine='polars-gpu' requires the RAPIDS cudf_polars stack` without it. So any environment with cuDF installed and `cudf_polars` absent (a common developer configuration) got 20 failures indistinguishable from product breakage, in a file that IS in the polars lane. CI never caught it because that lane installs neither package, so the parameter did not exist there — a gate can only fabricate failures where it is never exercised. The gate now tests `cudf_polars`. Measured: 20 failed / 116 passed before, 105 passed / 0 failed after, on a box with cuDF 25.10 and no `cudf_polars`. - **`is_lazy` is a type predicate, so the polars engine's eager/lazy split is checked instead of asserted**: `dtypes.is_lazy` decides WHICH member of the two-member `PolarsFrame` union a frame is, but it was declared `-> bool`, so that answer was discarded at the call boundary — an eager-only attribute (`.height`, `.columns`, `.schema`) reached after a lazy guard was unprovable, and the native polars chain closed the gap with `cast("pl.DataFrame", frame)`. A cast is not a type: it re-asserts, unchecked, the exact fact the predicate had just established, once per call site, and a wrong one surfaces as a production `AttributeError` rather than a CI error. `is_lazy` is now declared `-> TypeIs["pl.LazyFrame"]` (PEP 742), which narrows the *negative* branch as well as the positive — the eager side is the one that needed it, so `TypeGuard` would not have done — and the cast is gone. Four previously untyped chain-combine helpers (`_semi`, `_combine_edges`, `_apply_node_names`, and the `_known_empty` guard) now carry real signatures: `_semi`'s two frames share the `PolarsT` TypeVar because polars joins do not mix eagerness, and the two combine helpers take the `_LazyShim` collect-once duck-type they are actually called with. Typing `_apply_node_names` also retired a `getattr(next_step, "edges_empty", None)` — the shim declares that tri-state in `__slots__`, so a typo is now a checker error instead of a silent `None` that would have re-armed the very cardinality gate the guard disarms. Internal only: no runtime behaviour, no public API, and the `TypeIs` import is `TYPE_CHECKING`-only, so no new runtime dependency floor. - **GFQL execution context is declared rather than attached at runtime**: the private `_gfql_*` per-execution fields (index policy and registry, row-pipeline base graph, carried seed nodes, edge aliases, shortest-path backend, and the indexed-bindings handoff) are now declared on `Plottable` with defaults on `PlotterBase`, instead of being set with `setattr` and read back with `getattr(..., default)`. No public API or behaviour change; internal call sites are typed, and hand-rolled `Plottable` stand-ins must now construct the full context. diff --git a/graphistry/tests/compute/gfql/index/test_index.py b/graphistry/tests/compute/gfql/index/test_index.py index 8ac7bee5ce..8316d95193 100644 --- a/graphistry/tests/compute/gfql/index/test_index.py +++ b/graphistry/tests/compute/gfql/index/test_index.py @@ -29,7 +29,13 @@ def _engines(): import polars # noqa out.append("polars") try: - import cudf # noqa + # `cudf_polars`, NOT `cudf`: engine='polars-gpu' is the cudf_polars GPU collect + # target and raises `ImportError: GFQL engine='polars-gpu' requires the RAPIDS + # cudf_polars stack` without it. Gating on `cudf` FABRICATED 20 failures on every + # box that has cuDF but not cudf_polars — a real configuration, and the failures + # are indistinguishable from product breakage. CI never caught it because its + # polars lane installs neither, so the parameter simply did not exist there. + import cudf_polars # noqa out.append("polars-gpu") except Exception: pass