Skip to content

Commit d15d1a0

Browse files
lmeyerovclaude
andcommitted
test(engine): cover moved frame/series primitives; widen polars lane cov to graphistry
The frame-helper move added dispatch code to graphistry/Engine.py, whose polars branches were exercised only via the reentry tests but NOT measured — the polars lane covered only `--cov=graphistry/compute`, so Engine.py (outside compute/) showed 0% on its polars branches and dragged changed-line coverage to 75%. Fix both halves: (1) add a dedicated unit test for all nine primitives across pandas and polars (polars cases guarded by _HAS_POLARS + registered in POLARS_TEST_FILES) plus series_to_pylist's arrow/pandas/tolist fallbacks and raise-paths; (2) widen the polars lane to `--cov=graphistry` (the #1727 pattern) so Engine.py's polars branches are measured. Audit-safe: coverage_audit's polars profile only enforces graphistry/compute/gfql/lazy/**, so Engine.py is not floored. Reproduced the exact changed-line gate locally (combined core+gfql+polars artifacts): 75.00% -> 98.96%, Engine.py changed lines 100%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y6dQEcjdazEnzuvuwf73ZL
1 parent c1c63f8 commit d15d1a0

2 files changed

Lines changed: 186 additions & 2 deletions

File tree

bin/test-polars.sh

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,14 @@ POLARS_TEST_FILES=(
3030
# index tests exercise the seeded-index hook in the polars hop entry (hop.py) — without
3131
# them the hook dominates the now-thin file and trips its per-file coverage floor
3232
graphistry/tests/compute/gfql/index/test_index.py
33+
# engine-agnostic frame/series primitives (graphistry/Engine.py) — the polars branches of
34+
# these dispatch helpers are only measured when this lane covers graphistry (see cov widen below)
35+
graphistry/tests/test_engine_frame_helpers.py
3336
)
3437

3538
COV_ARGS=()
3639
if [ -n "${POLARS_COV:-}" ]; then
37-
COV_ARGS=(--cov=graphistry/compute --cov-report=)
40+
COV_ARGS=(--cov=graphistry --cov-report=)
3841
fi
3942

4043
python -B -m pytest -vv "${COV_ARGS[@]}" "${POLARS_TEST_FILES[@]}" "$@"
@@ -43,7 +46,7 @@ python -B -m pytest -vv "${COV_ARGS[@]}" "${POLARS_TEST_FILES[@]}" "$@"
4346
# appended into the same coverage data file when POLARS_COV=1 (CI audit reads it)
4447
COV_APPEND_ARGS=()
4548
if [ -n "${POLARS_COV:-}" ]; then
46-
COV_APPEND_ARGS=(--cov=graphistry/compute --cov-report= --cov-append)
49+
COV_APPEND_ARGS=(--cov=graphistry --cov-report= --cov-append)
4750
fi
4851
python -B -m pytest -vv "${COV_APPEND_ARGS[@]}" \
4952
graphistry/tests/compute/gfql/cypher/test_lowering.py -k polars
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
"""Unit tests for the engine-agnostic frame/series primitives in ``graphistry.Engine``.
2+
3+
These helpers (moved out of the cypher reentry executor) dispatch across
4+
pandas/cuDF/polars, so both branches of each are exercised here. Polars cases are
5+
skipped where polars is not installed (the pandas-only CI lanes); the polars lane
6+
(``bin/test-polars.sh``) runs this file with polars present.
7+
"""
8+
import pandas as pd
9+
import pytest
10+
11+
from graphistry.Engine import (
12+
assign_constant_columns,
13+
drop_columns,
14+
frame_filter,
15+
is_series_like,
16+
ordered_left_join,
17+
row_as_mapping,
18+
series_filter,
19+
series_not_null_mask,
20+
series_to_pylist,
21+
)
22+
23+
try:
24+
import polars as pl
25+
_HAS_POLARS = True
26+
except ImportError:
27+
_HAS_POLARS = False
28+
29+
polars_only = pytest.mark.skipif(not _HAS_POLARS, reason="polars not installed")
30+
31+
32+
# --- pandas branches (run on every lane) ---------------------------------
33+
34+
def test_is_series_like_pandas() -> None:
35+
assert is_series_like(pd.Series([1, 2])) is True
36+
assert is_series_like(object()) is False
37+
38+
39+
def test_series_not_null_mask_pandas() -> None:
40+
assert list(series_not_null_mask(pd.Series([1, None, 3]))) == [True, False, True]
41+
42+
43+
def test_series_filter_pandas_drops_index() -> None:
44+
s = pd.Series([10, 20, 30])
45+
out = series_filter(s, pd.Series([True, False, True]))
46+
assert list(out) == [10, 30]
47+
assert list(out.index) == [0, 1] # index reset
48+
49+
50+
def test_frame_filter_pandas_drops_index() -> None:
51+
df = pd.DataFrame({"a": [1, 2, 3]})
52+
out = frame_filter(df, pd.Series([False, True, True]))
53+
assert out["a"].tolist() == [2, 3]
54+
assert list(out.index) == [0, 1]
55+
56+
57+
def test_ordered_left_join_pandas_preserves_left_order() -> None:
58+
left = pd.DataFrame({"k": [3, 1, 2]})
59+
right = pd.DataFrame({"k": [1, 2, 3], "v": [10, 20, 30]})
60+
out = ordered_left_join(left, right, on="k")
61+
assert out["v"].tolist() == [30, 10, 20]
62+
63+
64+
def test_row_as_mapping_pandas() -> None:
65+
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
66+
assert dict(row_as_mapping(df, 1)) == {"a": 2, "b": 4}
67+
68+
69+
def test_assign_constant_columns_pandas_and_empty() -> None:
70+
df = pd.DataFrame({"a": [1, 2]})
71+
assert assign_constant_columns(df, {"c": 9})["c"].tolist() == [9, 9]
72+
# empty values short-circuits and returns the frame unchanged
73+
same = assign_constant_columns(df, {})
74+
assert same is df
75+
76+
77+
def test_drop_columns_pandas() -> None:
78+
df = pd.DataFrame({"a": [1], "b": [2], "c": [3]})
79+
assert list(drop_columns(df, ["b", "c"]).columns) == ["a"]
80+
81+
82+
def test_series_to_pylist_pandas_and_fallbacks() -> None:
83+
# pandas Series has no to_arrow -> tolist branch
84+
assert series_to_pylist(pd.Series([1, 2, 3])) == [1, 2, 3]
85+
86+
# object exposing to_pandas but not to_arrow (cuDF-shaped) -> to_pandas branch
87+
class _ToPandasOnly:
88+
def to_pandas(self) -> pd.Series:
89+
return pd.Series([7, 8])
90+
91+
assert series_to_pylist(_ToPandasOnly()) == [7, 8]
92+
93+
# to_arrow that raises -> falls through the except to the next branch
94+
class _ArrowRaises:
95+
def to_arrow(self): # type: ignore[no-untyped-def]
96+
raise RuntimeError("boom")
97+
98+
def tolist(self): # type: ignore[no-untyped-def]
99+
return [5, 6]
100+
101+
assert series_to_pylist(_ArrowRaises()) == [5, 6]
102+
103+
# to_pandas that raises -> falls through its except to the tolist branch
104+
class _PandasRaises:
105+
def to_pandas(self): # type: ignore[no-untyped-def]
106+
raise RuntimeError("boom")
107+
108+
def tolist(self): # type: ignore[no-untyped-def]
109+
return [3, 4]
110+
111+
assert series_to_pylist(_PandasRaises()) == [3, 4]
112+
113+
# tolist that raises -> falls through its except to the final list() fallback
114+
class _TolistRaisesIterable:
115+
def tolist(self): # type: ignore[no-untyped-def]
116+
raise RuntimeError("boom")
117+
118+
def __iter__(self): # type: ignore[no-untyped-def]
119+
return iter([9, 10])
120+
121+
assert series_to_pylist(_TolistRaisesIterable()) == [9, 10]
122+
123+
# no arrow/pandas/tolist -> final list() fallback over an iterable
124+
assert series_to_pylist([1, 2]) == [1, 2]
125+
126+
127+
# --- polars branches (polars lane only) ----------------------------------
128+
129+
@polars_only
130+
def test_is_series_like_polars() -> None:
131+
assert is_series_like(pl.Series([1, 2])) is True
132+
133+
134+
@polars_only
135+
def test_series_not_null_mask_polars() -> None:
136+
assert series_not_null_mask(pl.Series([1, None, 3])).to_list() == [True, False, True]
137+
138+
139+
@polars_only
140+
def test_series_filter_polars() -> None:
141+
out = series_filter(pl.Series([10, 20, 30]), pl.Series([True, False, True]))
142+
assert out.to_list() == [10, 30]
143+
144+
145+
@polars_only
146+
def test_frame_filter_polars() -> None:
147+
df = pl.DataFrame({"a": [1, 2, 3]})
148+
out = frame_filter(df, pl.Series([False, True, True]))
149+
assert out["a"].to_list() == [2, 3]
150+
151+
152+
@polars_only
153+
def test_ordered_left_join_polars_preserves_order_and_coerces_right() -> None:
154+
left = pl.DataFrame({"k": [3, 1, 2]})
155+
# right is pandas -> must be coerced to polars before the join
156+
right = pd.DataFrame({"k": [1, 2, 3], "v": [10, 20, 30]})
157+
out = ordered_left_join(left, right, on="k")
158+
assert out["v"].to_list() == [30, 10, 20]
159+
160+
161+
@polars_only
162+
def test_row_as_mapping_polars() -> None:
163+
df = pl.DataFrame({"a": [1, 2], "b": [3, 4]})
164+
assert dict(row_as_mapping(df, 1)) == {"a": 2, "b": 4}
165+
166+
167+
@polars_only
168+
def test_assign_constant_columns_polars() -> None:
169+
df = pl.DataFrame({"a": [1, 2]})
170+
assert assign_constant_columns(df, {"c": 9})["c"].to_list() == [9, 9]
171+
172+
173+
@polars_only
174+
def test_drop_columns_polars() -> None:
175+
df = pl.DataFrame({"a": [1], "b": [2], "c": [3]})
176+
assert list(drop_columns(df, ["b", "c"]).columns) == ["a"]
177+
178+
179+
@polars_only
180+
def test_series_to_pylist_polars_to_arrow() -> None:
181+
assert series_to_pylist(pl.Series([1, 2, 3])) == [1, 2, 3]

0 commit comments

Comments
 (0)